Browse Source

Add `createCollection(…)` overload accepting a customizer function for `CollectionOptions`.

Original Pull Request: #4979
issue/4969
Mark Paluch 7 months ago committed by Christoph Strobl
parent
commit
4e53fa792e
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 29
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
  2. 13
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  3. 29
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
  4. 23
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  5. 74
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
  6. 33
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java

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

@ -20,6 +20,7 @@ import java.util.List; @@ -20,6 +20,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
@ -269,15 +270,39 @@ public interface MongoOperations extends FluentMongoOperations { @@ -269,15 +270,39 @@ public interface MongoOperations extends FluentMongoOperations {
* <li>TimeSeries time and meta fields, granularity and {@code expireAfter}</li>
* </ul>
* Any other options such as change stream options, schema-based details (validation, encryption) are not considered
* and must be provided through {@link #createCollection(Class, CollectionOptions)} or
* {@link #createCollection(String, CollectionOptions)}.
* and must be provided through {@link #createCollection(Class, Function)} or
* {@link #createCollection(Class, CollectionOptions)}.
*
* @param entityClass class that determines the collection to create.
* @return the created collection.
* @see #createCollection(Class, Function)
* @see #createCollection(Class, CollectionOptions)
*/
<T> MongoCollection<Document> createCollection(Class<T> entityClass);
/**
* Create an uncapped collection with a name based on the provided entity class allowing to customize derived
* {@link CollectionOptions}.
* <p>
* This method derives {@link CollectionOptions} from the given {@code entityClass} using
* {@link org.springframework.data.mongodb.core.mapping.Document} and
* {@link org.springframework.data.mongodb.core.mapping.TimeSeries} annotations to determine:
* <ul>
* <li>Collation</li>
* <li>TimeSeries time and meta fields, granularity and {@code expireAfter}</li>
* </ul>
* Any other options such as change stream options, schema-based details (validation, encryption) are not considered
* and must be provided through {@link CollectionOptions}.
*
* @param entityClass class that determines the collection to create.
* @param collectionOptionsCustomizer customizer function to customize the derived {@link CollectionOptions}.
* @return the created collection.
* @see #createCollection(Class, CollectionOptions)
* @since 5.0
*/
<T> MongoCollection<Document> createCollection(Class<T> entityClass,
Function<? super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer);
/**
* Create a collection with a name based on the provided entity class using the options.
*

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

@ -23,6 +23,7 @@ import java.math.RoundingMode; @@ -23,6 +23,7 @@ import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -650,7 +651,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -650,7 +651,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Override
public <T> MongoCollection<Document> createCollection(Class<T> entityClass) {
return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions());
return createCollection(entityClass, Function.identity());
}
@Override
public <T> MongoCollection<Document> createCollection(Class<T> entityClass,
Function<? super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer) {
Assert.notNull(collectionOptionsCustomizer, "CollectionOptions customizer function must not be null");
return createCollection(entityClass,
collectionOptionsCustomizer.apply(operations.forType(entityClass).getCollectionOptions()));
}
@Override

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

@ -20,6 +20,7 @@ import reactor.core.publisher.Mono; @@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.bson.Document;
@ -223,15 +224,39 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -223,15 +224,39 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* <li>TimeSeries time and meta fields, granularity and {@code expireAfter}</li>
* </ul>
* Any other options such as change stream options, schema-based details (validation, encryption) are not considered
* and must be provided through {@link #createCollection(Class, CollectionOptions)} or
* {@link #createCollection(String, CollectionOptions)}.
* and must be provided through {@link #createCollection(Class, Function)} or
* {@link #createCollection(Class, CollectionOptions)}.
*
* @param entityClass class that determines the collection to create.
* @return the created collection.
* @see #createCollection(Class, Function)
* @see #createCollection(Class, CollectionOptions)
*/
<T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass);
/**
* Create an uncapped collection with a name based on the provided entity class allowing to customize derived
* {@link CollectionOptions}.
* <p>
* This method derives {@link CollectionOptions} from the given {@code entityClass} using
* {@link org.springframework.data.mongodb.core.mapping.Document} and
* {@link org.springframework.data.mongodb.core.mapping.TimeSeries} annotations to determine:
* <ul>
* <li>Collation</li>
* <li>TimeSeries time and meta fields, granularity and {@code expireAfter}</li>
* </ul>
* Any other options such as change stream options, schema-based details (validation, encryption) are not considered
* and must be provided through {@link CollectionOptions}.
*
* @param entityClass class that determines the collection to create.
* @param collectionOptionsCustomizer customizer function to customize the derived {@link CollectionOptions}.
* @return the created collection.
* @see #createCollection(Class, CollectionOptions)
* @since 5.0
*/
<T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass,
Function<? super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer);
/**
* Create a collection with a name based on the provided entity class using the options.
*

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

@ -659,7 +659,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -659,7 +659,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
@Override
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass) {
return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions());
return createCollection(entityClass, Function.identity());
}
@Override
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass,
Function<? super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer) {
Assert.notNull(collectionOptionsCustomizer, "CollectionOptions customizer function must not be null");
return createCollection(entityClass,
collectionOptionsCustomizer.apply(operations.forType(entityClass).getCollectionOptions()));
}
@Override
@ -740,11 +750,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -740,11 +750,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
@Override
public Mono<Boolean> collectionExists(String collectionName) {
return createMono(
db -> Flux.from(db.listCollectionNames()) //
.filter(s -> s.equals(collectionName)) //
.map(s -> true) //
.single(false));
return createMono(db -> Flux.from(db.listCollectionNames()) //
.filter(s -> s.equals(collectionName)) //
.map(s -> true) //
.single(false));
}
@Override
@ -2293,7 +2302,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2293,7 +2302,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
.flatMapSequential(deleteResult -> Flux.fromIterable(list)));
}
@SuppressWarnings({"rawtypes", "unchecked", "NullAway"})
@SuppressWarnings({ "rawtypes", "unchecked", "NullAway" })
<S, T> Flux<T> doFindAndDelete(String collectionName, Query query, Class<S> entityClass,
QueryResultConverter<? super S, ? extends T> resultConverter) {

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

@ -186,11 +186,13 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -186,11 +186,13 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
when(collection.withReadConcern(any())).thenReturn(collection);
when(collection.withReadPreference(any())).thenReturn(collection);
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
.thenReturn(updateResult);
when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctIterable);
when(collectionWithWriteConcern.deleteOne(any(Bson.class), any())).thenReturn(deleteResult);
when(collectionWithWriteConcern.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
when(collectionWithWriteConcern.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
.thenReturn(updateResult);
when(findIterable.projection(any())).thenReturn(findIterable);
when(findIterable.sort(any(org.bson.Document.class))).thenReturn(findIterable);
when(findIterable.collation(any())).thenReturn(findIterable);
@ -1263,7 +1265,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1263,7 +1265,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
template.save(entity);
verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(), any(com.mongodb.client.model.ReplaceOptions.class));
verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(),
any(com.mongodb.client.model.ReplaceOptions.class));
assertThat(queryCaptor.getValue()).isEqualTo(new Document("_id", 1).append("version", 10));
assertThat(updateCaptor.getValue())
@ -1399,10 +1402,14 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1399,10 +1402,14 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
Assertions.assertThat(options.getValue().getCollation()).isNull();
}
@Test // DATAMONGO-1854
@Test // DATAMONGO-1854, GH-4978
void createCollectionShouldApplyDefaultCollation() {
template.createCollection(Sith.class);
template.createCollection(Sith.class, options -> {
assertThat(options.getCollation()).contains(Collation.of("de_AT"));
return options;
});
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@ -1426,7 +1433,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1426,7 +1433,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-1854
void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
template.createCollection(Sith.class, null);
template.createCollection(Sith.class, (CollectionOptions) null);
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@ -2399,8 +2406,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2399,8 +2406,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES))
.isEqualTo(10);
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES)).isEqualTo(10);
}
@Test // GH-4099
@ -2413,8 +2419,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2413,8 +2419,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES))
.isEqualTo(12);
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES)).isEqualTo(12);
}
@Test // GH-4099
@ -2425,8 +2430,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2425,8 +2430,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS))
.isEqualTo(1);
assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS)).isEqualTo(1);
}
@Test // GH-4099
@ -2437,8 +2441,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2437,8 +2441,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
.isEqualTo(11);
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(11);
}
@Test // GH-4099
@ -2449,16 +2452,14 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2449,16 +2452,14 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
.isEqualTo(100);
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(100);
}
@Test // GH-4099
void createCollectionShouldSetUpTimeSeriesWithInvalidTimeoutExpiration() {
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() ->
template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class)
);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class));
}
@Test // GH-3522
@ -2611,32 +2612,31 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2611,32 +2612,31 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(collection).withWriteConcern(eq(WriteConcern.UNACKNOWLEDGED));
}
@Test // GH-4099
void passOnTimeSeriesExpireOption() {
template.createCollection("time-series-collection",
CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(10))));
@Test // GH-4099
void passOnTimeSeriesExpireOption() {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
template.createCollection("time-series-collection",
CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(10))));
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(10);
}
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@Test // GH-4099
void doNotSetTimeSeriesExpireOptionForNegativeValue() {
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(10);
}
template.createCollection("time-series-collection",
CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(-10))));
@Test // GH-4099
void doNotSetTimeSeriesExpireOptionForNegativeValue() {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
template.createCollection("time-series-collection",
CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(-10))));
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(0L);
}
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(0L);
}
class AutogenerateableId {
class AutogenerateableId {
@Id BigInteger id;
}

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

@ -456,8 +456,10 @@ public class ReactiveMongoTemplateUnitTests { @@ -456,8 +456,10 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1719
void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
template.doFind("star-wars", CollectionPreparer.identity(), new Document(), new Document(), Person.class,
PersonSpELProjection.class, QueryResultConverter.entity(), FindPublisherPreparer.NO_OP_PREPARER).subscribe();
template
.doFind("star-wars", CollectionPreparer.identity(), new Document(), new Document(), Person.class,
PersonSpELProjection.class, QueryResultConverter.entity(), FindPublisherPreparer.NO_OP_PREPARER)
.subscribe();
verify(findPublisher, never()).projection(any());
}
@ -638,10 +640,14 @@ public class ReactiveMongoTemplateUnitTests { @@ -638,10 +640,14 @@ public class ReactiveMongoTemplateUnitTests {
Assertions.assertThat(options.getValue().getCollation()).isNull();
}
@Test // DATAMONGO-1854
@Test // DATAMONGO-1854, GH-4978
void createCollectionShouldApplyDefaultCollation() {
template.createCollection(Sith.class).subscribe();
template.createCollection(Sith.class, options -> {
assertThat(options.getCollation()).contains(Collation.of("de_AT"));
return options;
}).subscribe();
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@ -665,7 +671,7 @@ public class ReactiveMongoTemplateUnitTests { @@ -665,7 +671,7 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1854
void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
template.createCollection(Sith.class, null).subscribe();
template.createCollection(Sith.class, (CollectionOptions) null).subscribe();
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@ -1751,8 +1757,7 @@ public class ReactiveMongoTemplateUnitTests { @@ -1751,8 +1757,7 @@ public class ReactiveMongoTemplateUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES))
.isEqualTo(10);
assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES)).isEqualTo(10);
}
@Test // GH-4099
@ -1763,8 +1768,7 @@ public class ReactiveMongoTemplateUnitTests { @@ -1763,8 +1768,7 @@ public class ReactiveMongoTemplateUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS))
.isEqualTo(1);
assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS)).isEqualTo(1);
}
@Test // GH-4099
@ -1775,8 +1779,7 @@ public class ReactiveMongoTemplateUnitTests { @@ -1775,8 +1779,7 @@ public class ReactiveMongoTemplateUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
.isEqualTo(11);
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(11);
}
@Test // GH-4099
@ -1787,16 +1790,14 @@ public class ReactiveMongoTemplateUnitTests { @@ -1787,16 +1790,14 @@ public class ReactiveMongoTemplateUnitTests {
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
.isEqualTo(100);
assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(100);
}
@Test // GH-4099
void createCollectionShouldSetUpTimeSeriesWithInvalidTimeoutExpiration() {
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() ->
template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class).subscribe()
);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class).subscribe());
}
private void stubFindSubscribe(Document document) {

Loading…
Cancel
Save