Skip to content

Commit 4e53fa7

Browse files
mp911dechristophstrobl
authored andcommitted
Add createCollection(…) overload accepting a customizer function for CollectionOptions.
Original Pull Request: #4979
1 parent 172a7d1 commit 4e53fa7

File tree

6 files changed

+136
-65
lines changed

6 files changed

+136
-65
lines changed

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Set;
2121
import java.util.concurrent.locks.ReentrantLock;
2222
import java.util.function.Consumer;
23+
import java.util.function.Function;
2324
import java.util.function.Supplier;
2425
import java.util.stream.Stream;
2526

@@ -269,15 +270,39 @@ default SessionScoped withSession(Supplier<ClientSession> sessionProvider) {
269270
* <li>TimeSeries time and meta fields, granularity and {@code expireAfter}</li>
270271
* </ul>
271272
* Any other options such as change stream options, schema-based details (validation, encryption) are not considered
272-
* and must be provided through {@link #createCollection(Class, CollectionOptions)} or
273-
* {@link #createCollection(String, CollectionOptions)}.
273+
* and must be provided through {@link #createCollection(Class, Function)} or
274+
* {@link #createCollection(Class, CollectionOptions)}.
274275
*
275276
* @param entityClass class that determines the collection to create.
276277
* @return the created collection.
278+
* @see #createCollection(Class, Function)
277279
* @see #createCollection(Class, CollectionOptions)
278280
*/
279281
<T> MongoCollection<Document> createCollection(Class<T> entityClass);
280282

283+
/**
284+
* Create an uncapped collection with a name based on the provided entity class allowing to customize derived
285+
* {@link CollectionOptions}.
286+
* <p>
287+
* This method derives {@link CollectionOptions} from the given {@code entityClass} using
288+
* {@link org.springframework.data.mongodb.core.mapping.Document} and
289+
* {@link org.springframework.data.mongodb.core.mapping.TimeSeries} annotations to determine:
290+
* <ul>
291+
* <li>Collation</li>
292+
* <li>TimeSeries time and meta fields, granularity and {@code expireAfter}</li>
293+
* </ul>
294+
* Any other options such as change stream options, schema-based details (validation, encryption) are not considered
295+
* and must be provided through {@link CollectionOptions}.
296+
*
297+
* @param entityClass class that determines the collection to create.
298+
* @param collectionOptionsCustomizer customizer function to customize the derived {@link CollectionOptions}.
299+
* @return the created collection.
300+
* @see #createCollection(Class, CollectionOptions)
301+
* @since 5.0
302+
*/
303+
<T> MongoCollection<Document> createCollection(Class<T> entityClass,
304+
Function<? super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer);
305+
281306
/**
282307
* Create a collection with a name based on the provided entity class using the options.
283308
*

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.*;
2424
import java.util.concurrent.TimeUnit;
2525
import java.util.function.BiPredicate;
26+
import java.util.function.Function;
2627
import java.util.stream.Collectors;
2728
import java.util.stream.Stream;
2829

@@ -650,7 +651,17 @@ public void setSessionSynchronization(SessionSynchronization sessionSynchronizat
650651

651652
@Override
652653
public <T> MongoCollection<Document> createCollection(Class<T> entityClass) {
653-
return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions());
654+
return createCollection(entityClass, Function.identity());
655+
}
656+
657+
@Override
658+
public <T> MongoCollection<Document> createCollection(Class<T> entityClass,
659+
Function<? super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer) {
660+
661+
Assert.notNull(collectionOptionsCustomizer, "CollectionOptions customizer function must not be null");
662+
663+
return createCollection(entityClass,
664+
collectionOptionsCustomizer.apply(operations.forType(entityClass).getCollectionOptions()));
654665
}
655666

656667
@Override

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.util.Collection;
2222
import java.util.function.Consumer;
23+
import java.util.function.Function;
2324
import java.util.function.Supplier;
2425

2526
import org.bson.Document;
@@ -223,15 +224,39 @@ default ReactiveSessionScoped withSession(Supplier<ClientSession> sessionProvide
223224
* <li>TimeSeries time and meta fields, granularity and {@code expireAfter}</li>
224225
* </ul>
225226
* Any other options such as change stream options, schema-based details (validation, encryption) are not considered
226-
* and must be provided through {@link #createCollection(Class, CollectionOptions)} or
227-
* {@link #createCollection(String, CollectionOptions)}.
227+
* and must be provided through {@link #createCollection(Class, Function)} or
228+
* {@link #createCollection(Class, CollectionOptions)}.
228229
*
229230
* @param entityClass class that determines the collection to create.
230231
* @return the created collection.
232+
* @see #createCollection(Class, Function)
231233
* @see #createCollection(Class, CollectionOptions)
232234
*/
233235
<T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass);
234236

237+
/**
238+
* Create an uncapped collection with a name based on the provided entity class allowing to customize derived
239+
* {@link CollectionOptions}.
240+
* <p>
241+
* This method derives {@link CollectionOptions} from the given {@code entityClass} using
242+
* {@link org.springframework.data.mongodb.core.mapping.Document} and
243+
* {@link org.springframework.data.mongodb.core.mapping.TimeSeries} annotations to determine:
244+
* <ul>
245+
* <li>Collation</li>
246+
* <li>TimeSeries time and meta fields, granularity and {@code expireAfter}</li>
247+
* </ul>
248+
* Any other options such as change stream options, schema-based details (validation, encryption) are not considered
249+
* and must be provided through {@link CollectionOptions}.
250+
*
251+
* @param entityClass class that determines the collection to create.
252+
* @param collectionOptionsCustomizer customizer function to customize the derived {@link CollectionOptions}.
253+
* @return the created collection.
254+
* @see #createCollection(Class, CollectionOptions)
255+
* @since 5.0
256+
*/
257+
<T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass,
258+
Function<? super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer);
259+
235260
/**
236261
* Create a collection with a name based on the provided entity class using the options.
237262
*

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,17 @@ public <T> Mono<T> createMono(String collectionName, ReactiveCollectionCallback<
659659

660660
@Override
661661
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass) {
662-
return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions());
662+
return createCollection(entityClass, Function.identity());
663+
}
664+
665+
@Override
666+
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass,
667+
Function<? super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer) {
668+
669+
Assert.notNull(collectionOptionsCustomizer, "CollectionOptions customizer function must not be null");
670+
671+
return createCollection(entityClass,
672+
collectionOptionsCustomizer.apply(operations.forType(entityClass).getCollectionOptions()));
663673
}
664674

665675
@Override
@@ -740,11 +750,10 @@ public <T> Mono<Boolean> collectionExists(Class<T> entityClass) {
740750

741751
@Override
742752
public Mono<Boolean> collectionExists(String collectionName) {
743-
return createMono(
744-
db -> Flux.from(db.listCollectionNames()) //
745-
.filter(s -> s.equals(collectionName)) //
746-
.map(s -> true) //
747-
.single(false));
753+
return createMono(db -> Flux.from(db.listCollectionNames()) //
754+
.filter(s -> s.equals(collectionName)) //
755+
.map(s -> true) //
756+
.single(false));
748757
}
749758

750759
@Override
@@ -2293,7 +2302,7 @@ protected <T> Flux<T> doFindAndDelete(String collectionName, Query query, Class<
22932302
.flatMapSequential(deleteResult -> Flux.fromIterable(list)));
22942303
}
22952304

2296-
@SuppressWarnings({"rawtypes", "unchecked", "NullAway"})
2305+
@SuppressWarnings({ "rawtypes", "unchecked", "NullAway" })
22972306
<S, T> Flux<T> doFindAndDelete(String collectionName, Query query, Class<S> entityClass,
22982307
QueryResultConverter<? super S, ? extends T> resultConverter) {
22992308

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,13 @@ void beforeEach() {
186186
when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
187187
when(collection.withReadConcern(any())).thenReturn(collection);
188188
when(collection.withReadPreference(any())).thenReturn(collection);
189-
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
189+
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
190+
.thenReturn(updateResult);
190191
when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
191192
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctIterable);
192193
when(collectionWithWriteConcern.deleteOne(any(Bson.class), any())).thenReturn(deleteResult);
193-
when(collectionWithWriteConcern.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
194+
when(collectionWithWriteConcern.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
195+
.thenReturn(updateResult);
194196
when(findIterable.projection(any())).thenReturn(findIterable);
195197
when(findIterable.sort(any(org.bson.Document.class))).thenReturn(findIterable);
196198
when(findIterable.collation(any())).thenReturn(findIterable);
@@ -1263,7 +1265,8 @@ void saveVersionedEntityShouldCallUpdateCorrectly() {
12631265

12641266
template.save(entity);
12651267

1266-
verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(), any(com.mongodb.client.model.ReplaceOptions.class));
1268+
verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(),
1269+
any(com.mongodb.client.model.ReplaceOptions.class));
12671270

12681271
assertThat(queryCaptor.getValue()).isEqualTo(new Document("_id", 1).append("version", 10));
12691272
assertThat(updateCaptor.getValue())
@@ -1399,10 +1402,14 @@ void createCollectionShouldNotCollationIfNotPresent() {
13991402
Assertions.assertThat(options.getValue().getCollation()).isNull();
14001403
}
14011404

1402-
@Test // DATAMONGO-1854
1405+
@Test // DATAMONGO-1854, GH-4978
14031406
void createCollectionShouldApplyDefaultCollation() {
14041407

1405-
template.createCollection(Sith.class);
1408+
template.createCollection(Sith.class, options -> {
1409+
1410+
assertThat(options.getCollation()).contains(Collation.of("de_AT"));
1411+
return options;
1412+
});
14061413

14071414
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
14081415
verify(db).createCollection(any(), options.capture());
@@ -1426,7 +1433,7 @@ void createCollectionShouldFavorExplicitOptionsOverDefaultCollation() {
14261433
@Test // DATAMONGO-1854
14271434
void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
14281435

1429-
template.createCollection(Sith.class, null);
1436+
template.createCollection(Sith.class, (CollectionOptions) null);
14301437

14311438
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
14321439
verify(db).createCollection(any(), options.capture());
@@ -2399,8 +2406,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromString() {
23992406
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
24002407
verify(db).createCollection(any(), options.capture());
24012408

2402-
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES))
2403-
.isEqualTo(10);
2409+
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES)).isEqualTo(10);
24042410
}
24052411

24062412
@Test // GH-4099
@@ -2413,8 +2419,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromProperty() {
24132419
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
24142420
verify(db).createCollection(any(), options.capture());
24152421

2416-
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES))
2417-
.isEqualTo(12);
2422+
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES)).isEqualTo(12);
24182423
}
24192424

24202425
@Test // GH-4099
@@ -2425,8 +2430,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromIso8601String() {
24252430
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
24262431
verify(db).createCollection(any(), options.capture());
24272432

2428-
assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS))
2429-
.isEqualTo(1);
2433+
assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS)).isEqualTo(1);
24302434
}
24312435

24322436
@Test // GH-4099
@@ -2437,8 +2441,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromExpression() {
24372441
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
24382442
verify(db).createCollection(any(), options.capture());
24392443

2440-
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
2441-
.isEqualTo(11);
2444+
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(11);
24422445
}
24432446

24442447
@Test // GH-4099
@@ -2449,16 +2452,14 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromExpressionReturningD
24492452
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
24502453
verify(db).createCollection(any(), options.capture());
24512454

2452-
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
2453-
.isEqualTo(100);
2455+
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(100);
24542456
}
24552457

24562458
@Test // GH-4099
24572459
void createCollectionShouldSetUpTimeSeriesWithInvalidTimeoutExpiration() {
24582460

2459-
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() ->
2460-
template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class)
2461-
);
2461+
assertThatExceptionOfType(IllegalArgumentException.class)
2462+
.isThrownBy(() -> template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class));
24622463
}
24632464

24642465
@Test // GH-3522
@@ -2611,32 +2612,31 @@ public WriteConcern resolve(MongoAction action) {
26112612
verify(collection).withWriteConcern(eq(WriteConcern.UNACKNOWLEDGED));
26122613
}
26132614

2614-
@Test // GH-4099
2615-
void passOnTimeSeriesExpireOption() {
2616-
2617-
template.createCollection("time-series-collection",
2618-
CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(10))));
2615+
@Test // GH-4099
2616+
void passOnTimeSeriesExpireOption() {
26192617

2620-
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
2621-
verify(db).createCollection(any(), options.capture());
2618+
template.createCollection("time-series-collection",
2619+
CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(10))));
26222620

2623-
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(10);
2624-
}
2621+
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
2622+
verify(db).createCollection(any(), options.capture());
26252623

2626-
@Test // GH-4099
2627-
void doNotSetTimeSeriesExpireOptionForNegativeValue() {
2624+
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(10);
2625+
}
26282626

2629-
template.createCollection("time-series-collection",
2630-
CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(-10))));
2627+
@Test // GH-4099
2628+
void doNotSetTimeSeriesExpireOptionForNegativeValue() {
26312629

2632-
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
2633-
verify(db).createCollection(any(), options.capture());
2630+
template.createCollection("time-series-collection",
2631+
CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(-10))));
26342632

2635-
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(0L);
2636-
}
2633+
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
2634+
verify(db).createCollection(any(), options.capture());
26372635

2636+
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(0L);
2637+
}
26382638

2639-
class AutogenerateableId {
2639+
class AutogenerateableId {
26402640

26412641
@Id BigInteger id;
26422642
}

0 commit comments

Comments
 (0)