From 7b53b050c75bc1306e4fa2614d09ea748e9a1eb2 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 25 Mar 2025 09:34:02 +0100 Subject: [PATCH] rename some of the things --- .../data/mongodb/core/CollectionOptions.java | 147 +++++++++--------- .../data/mongodb/core/EntityOperations.java | 4 +- .../data/mongodb/core/MongoTemplate.java | 1 - .../core/CollectionOptionsUnitTests.java | 30 +++- .../core/encryption/RangeEncryptionTests.java | 6 +- 5 files changed, 104 insertions(+), 84 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java index d39d9de01..28215dc64 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java @@ -70,12 +70,12 @@ public class CollectionOptions { private ValidationOptions validationOptions; private @Nullable TimeSeriesOptions timeSeriesOptions; private @Nullable CollectionChangeStreamOptions changeStreamOptions; - private @Nullable EncryptedCollectionOptions encryptedCollectionOptions; + private @Nullable EncryptedFieldsOptions encryptedFieldsOptions; private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped, @Nullable Collation collation, ValidationOptions validationOptions, @Nullable TimeSeriesOptions timeSeriesOptions, @Nullable CollectionChangeStreamOptions changeStreamOptions, - @Nullable EncryptedCollectionOptions encryptedCollectionOptions) { + @Nullable EncryptedFieldsOptions encryptedFieldsOptions) { this.maxDocuments = maxDocuments; this.size = size; @@ -84,7 +84,7 @@ public class CollectionOptions { this.validationOptions = validationOptions; this.timeSeriesOptions = timeSeriesOptions; this.changeStreamOptions = changeStreamOptions; - this.encryptedCollectionOptions = encryptedCollectionOptions; + this.encryptedFieldsOptions = encryptedFieldsOptions; } /** @@ -149,6 +149,46 @@ public class CollectionOptions { return empty().changeStream(CollectionChangeStreamOptions.preAndPostImages(true)); } + /** + * Create new {@link CollectionOptions} with the given {@code encryptedFields}. + * + * @param encryptedFieldsOptions can be null + * @return new instance of {@link CollectionOptions}. + * @since 4.5.0 + */ + @Contract("_ -> new") + @CheckReturnValue + public static CollectionOptions encryptedCollection(@Nullable EncryptedFieldsOptions encryptedFieldsOptions) { + return new CollectionOptions(null, null, null, null, ValidationOptions.NONE, null, null, encryptedFieldsOptions); + } + + /** + * Create new {@link CollectionOptions} reading encryption options from the given {@link MongoJsonSchema}. + * + * @param schema must not be {@literal null}. + * @return new instance of {@link CollectionOptions}. + * @since 4.5.0 + */ + @Contract("_ -> new") + @CheckReturnValue + public static CollectionOptions encryptedCollection(MongoJsonSchema schema) { + return encryptedCollection(EncryptedFieldsOptions.fromSchema(schema)); + } + + /** + * Create new {@link CollectionOptions} building encryption options in a fluent style. + * + * @param optionsFunction must not be {@literal null}. + * @return new instance of {@link CollectionOptions}. + * @since 4.5.0 + */ + @Contract("_ -> new") + @CheckReturnValue + public static CollectionOptions encryptedCollection( + Function optionsFunction) { + return encryptedCollection(optionsFunction.apply(new EncryptedFieldsOptions())); + } + /** * Create new {@link CollectionOptions} with already given settings and capped set to {@literal true}.
* NOTE: Using capped collections requires defining {@link #size(long)}. @@ -158,7 +198,7 @@ public class CollectionOptions { */ public CollectionOptions capped() { return new CollectionOptions(size, maxDocuments, true, collation, validationOptions, timeSeriesOptions, - changeStreamOptions, encryptedCollectionOptions); + changeStreamOptions, encryptedFieldsOptions); } /** @@ -170,7 +210,7 @@ public class CollectionOptions { */ public CollectionOptions maxDocuments(long maxDocuments) { return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions, - changeStreamOptions, encryptedCollectionOptions); + changeStreamOptions, encryptedFieldsOptions); } /** @@ -182,7 +222,7 @@ public class CollectionOptions { */ public CollectionOptions size(long size) { return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions, - changeStreamOptions, encryptedCollectionOptions); + changeStreamOptions, encryptedFieldsOptions); } /** @@ -194,7 +234,7 @@ public class CollectionOptions { */ public CollectionOptions collation(@Nullable Collation collation) { return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions, - changeStreamOptions, encryptedCollectionOptions); + changeStreamOptions, encryptedFieldsOptions); } /** @@ -315,7 +355,7 @@ public class CollectionOptions { Assert.notNull(validationOptions, "ValidationOptions must not be null"); return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions, - changeStreamOptions, encryptedCollectionOptions); + changeStreamOptions, encryptedFieldsOptions); } /** @@ -329,7 +369,7 @@ public class CollectionOptions { Assert.notNull(timeSeriesOptions, "TimeSeriesOptions must not be null"); return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions, - changeStreamOptions, encryptedCollectionOptions); + changeStreamOptions, encryptedFieldsOptions); } /** @@ -343,57 +383,22 @@ public class CollectionOptions { Assert.notNull(changeStreamOptions, "ChangeStreamOptions must not be null"); return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions, - changeStreamOptions, encryptedCollectionOptions); - } - - @Contract("_ -> new") - @CheckReturnValue - public CollectionOptions encrypted(EncryptedCollectionOptions encryptedCollectionOptions) { - - Assert.notNull(encryptedCollectionOptions, "EncryptedCollectionOptions must not be null"); - return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions, - changeStreamOptions, encryptedCollectionOptions); + changeStreamOptions, encryptedFieldsOptions); } /** - * Create new {@link CollectionOptions} with the given {@code encryptedFields}. + * Set the {@link EncryptedFieldsOptions} for collections using queryable encryption. * - * @param encryptedCollectionOptions can be null + * @param encryptedFieldsOptions must not be {@literal null}. * @return new instance of {@link CollectionOptions}. - * @since 4.5.0 */ @Contract("_ -> new") @CheckReturnValue - public static CollectionOptions encryptedCollection(@Nullable EncryptedCollectionOptions encryptedCollectionOptions) { - return new CollectionOptions(null, null, null, null, ValidationOptions.NONE, null, null, - encryptedCollectionOptions); - } + public CollectionOptions encrypted(EncryptedFieldsOptions encryptedFieldsOptions) { - /** - * Create new {@link CollectionOptions} reading encryption options from the given {@link MongoJsonSchema}. - * - * @param schema must not be {@literal null}. - * @return new instance of {@link CollectionOptions}. - * @since 4.5.0 - */ - @Contract("_ -> new") - @CheckReturnValue - public static CollectionOptions encryptedCollection(MongoJsonSchema schema) { - return encryptedCollection(EncryptedCollectionOptions.fromSchema(schema)); - } - - /** - * Create new {@link CollectionOptions} building encryption options in a fluent style. - * - * @param optionsFunction must not be {@literal null}. - * @return new instance of {@link CollectionOptions}. - * @since 4.5.0 - */ - @Contract("_ -> new") - @CheckReturnValue - public static CollectionOptions encryptedCollection( - Function optionsFunction) { - return encryptedCollection(optionsFunction.apply(new EncryptedCollectionOptions())); + Assert.notNull(encryptedFieldsOptions, "EncryptedCollectionOptions must not be null"); + return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions, + changeStreamOptions, encryptedFieldsOptions); } /** @@ -470,8 +475,8 @@ public class CollectionOptions { * @return {@link Optional#empty()} if not specified. * @since 4.5.0 */ - public Optional getEncryptionOptions() { - return Optional.ofNullable(encryptedCollectionOptions); + public Optional getEncryptedFieldsOptions() { + return Optional.ofNullable(encryptedFieldsOptions); } @Override @@ -479,7 +484,7 @@ public class CollectionOptions { return "CollectionOptions{" + "maxDocuments=" + maxDocuments + ", size=" + size + ", capped=" + capped + ", collation=" + collation + ", validationOptions=" + validationOptions + ", timeSeriesOptions=" + timeSeriesOptions + ", changeStreamOptions=" + changeStreamOptions + ", encryptedCollectionOptions=" - + encryptedCollectionOptions + ", disableValidation=" + disableValidation() + ", strictValidation=" + + encryptedFieldsOptions + ", disableValidation=" + disableValidation() + ", strictValidation=" + strictValidation() + ", moderateValidation=" + moderateValidation() + ", warnOnValidationError=" + warnOnValidationError() + ", failOnValidationError=" + failOnValidationError() + '}'; } @@ -516,7 +521,7 @@ public class CollectionOptions { if (!ObjectUtils.nullSafeEquals(changeStreamOptions, that.changeStreamOptions)) { return false; } - return ObjectUtils.nullSafeEquals(encryptedCollectionOptions, that.encryptedCollectionOptions); + return ObjectUtils.nullSafeEquals(encryptedFieldsOptions, that.encryptedFieldsOptions); } @Override @@ -528,7 +533,7 @@ public class CollectionOptions { result = 31 * result + ObjectUtils.nullSafeHashCode(validationOptions); result = 31 * result + ObjectUtils.nullSafeHashCode(timeSeriesOptions); result = 31 * result + ObjectUtils.nullSafeHashCode(changeStreamOptions); - result = 31 * result + ObjectUtils.nullSafeHashCode(encryptedCollectionOptions); + result = 31 * result + ObjectUtils.nullSafeHashCode(encryptedFieldsOptions); return result; } @@ -668,39 +673,39 @@ public class CollectionOptions { * @author Christoph Strobl * @since 4.5 */ - public static class EncryptedCollectionOptions { + public static class EncryptedFieldsOptions { - private static final EncryptedCollectionOptions NONE = new EncryptedCollectionOptions(); + private static final EncryptedFieldsOptions NONE = new EncryptedFieldsOptions(); private @Nullable MongoJsonSchema schema; private List queryableProperties; /** - * @return {@link EncryptedCollectionOptions#NONE} + * @return {@link EncryptedFieldsOptions#NONE} */ - public static EncryptedCollectionOptions none() { + public static EncryptedFieldsOptions none() { return NONE; } /** - * @return new instance of {@link EncryptedCollectionOptions}. + * @return new instance of {@link EncryptedFieldsOptions}. */ - public static EncryptedCollectionOptions fromSchema(MongoJsonSchema schema) { - return new EncryptedCollectionOptions(schema, List.of()); + public static EncryptedFieldsOptions fromSchema(MongoJsonSchema schema) { + return new EncryptedFieldsOptions(schema, List.of()); } /** - * @return new instance of {@link EncryptedCollectionOptions}. + * @return new instance of {@link EncryptedFieldsOptions}. */ - public static EncryptedCollectionOptions fromProperties(List properties) { - return new EncryptedCollectionOptions(null, List.copyOf(properties)); + public static EncryptedFieldsOptions fromProperties(List properties) { + return new EncryptedFieldsOptions(null, List.copyOf(properties)); } - EncryptedCollectionOptions() { + EncryptedFieldsOptions() { this(null, List.of()); } - private EncryptedCollectionOptions(@Nullable MongoJsonSchema schema, + private EncryptedFieldsOptions(@Nullable MongoJsonSchema schema, List queryableProperties) { this.schema = schema; @@ -717,17 +722,17 @@ public class CollectionOptions { * {@link org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.EncryptedJsonSchemaProperty * encrypted}. * @param characteristics the query options to set. - * @return new instance of {@link EncryptedCollectionOptions}. + * @return new instance of {@link EncryptedFieldsOptions}. */ @Contract("_, _ -> new") @CheckReturnValue - public EncryptedCollectionOptions queryable(JsonSchemaProperty property, QueryCharacteristic... characteristics) { + public EncryptedFieldsOptions queryable(JsonSchemaProperty property, QueryCharacteristic... characteristics) { List targetPropertyList = new ArrayList<>(queryableProperties.size() + 1); targetPropertyList.addAll(queryableProperties); targetPropertyList.add(JsonSchemaProperty.queryable(property, List.of(characteristics))); - return new EncryptedCollectionOptions(schema, targetPropertyList); + return new EncryptedFieldsOptions(schema, targetPropertyList); } public Document toDocument() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java index 8520fa15c..24977c5af 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java @@ -39,7 +39,7 @@ import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; -import org.springframework.data.mongodb.core.CollectionOptions.EncryptedCollectionOptions; +import org.springframework.data.mongodb.core.CollectionOptions.EncryptedFieldsOptions; import org.springframework.data.mongodb.core.CollectionOptions.TimeSeriesOptions; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper; @@ -380,7 +380,7 @@ class EntityOperations { collectionOptions.getChangeStreamOptions().ifPresent(it -> result .changeStreamPreAndPostImagesOptions(new ChangeStreamPreAndPostImagesOptions(it.getPreAndPostImages()))); - collectionOptions.getEncryptionOptions().map(EncryptedCollectionOptions::toDocument).ifPresent(encryptedFields -> { + collectionOptions.getEncryptedFieldsOptions().map(EncryptedFieldsOptions::toDocument).ifPresent(encryptedFields -> { if (!encryptedFields.isEmpty()) { result.encryptedFields(encryptedFields); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 4e983819d..67ef3a308 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -58,7 +58,6 @@ import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.MongoDatabaseUtils; import org.springframework.data.mongodb.SessionSynchronization; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; -import org.springframework.data.mongodb.core.CollectionOptions.EncryptedCollectionOptions; import org.springframework.data.mongodb.core.CollectionPreparerSupport.CollectionPreparerDelegate; import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext; import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CollectionOptionsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CollectionOptionsUnitTests.java index 56c811ccb..9de0863cd 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CollectionOptionsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CollectionOptionsUnitTests.java @@ -16,11 +16,12 @@ package org.springframework.data.mongodb.core; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.mongodb.core.CollectionOptions.EncryptedCollectionOptions; +import static org.springframework.data.mongodb.core.CollectionOptions.EncryptedFieldsOptions; import static org.springframework.data.mongodb.core.CollectionOptions.TimeSeriesOptions; import static org.springframework.data.mongodb.core.CollectionOptions.emitChangedRevisions; import static org.springframework.data.mongodb.core.CollectionOptions.empty; import static org.springframework.data.mongodb.core.CollectionOptions.encryptedCollection; +import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.int32; import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.queryable; import java.util.List; @@ -31,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.schema.JsonSchemaProperty; import org.springframework.data.mongodb.core.schema.MongoJsonSchema; +import org.springframework.data.mongodb.core.schema.QueryCharacteristics; import org.springframework.data.mongodb.core.validation.Validator; /** @@ -96,7 +98,7 @@ class CollectionOptionsUnitTests { .properties(queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int32("data")), List.of()))) .property(queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int64("mongodb")), List.of())).build(); - EncryptedCollectionOptions encryptionOptions = EncryptedCollectionOptions.fromSchema(schema); + EncryptedFieldsOptions encryptionOptions = EncryptedFieldsOptions.fromSchema(schema); assertThat(encryptionOptions.toDocument().get("fields", List.class)).hasSize(2) .contains(new Document("path", "mongodb").append("bsonType", "long").append("queries", List.of()) @@ -116,7 +118,7 @@ class CollectionOptionsUnitTests { // override first with data type long .queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int64("spring")))); - assertThat(collectionOptions.getEncryptionOptions()).map(EncryptedCollectionOptions::toDocument) + assertThat(collectionOptions.getEncryptedFieldsOptions()).map(EncryptedFieldsOptions::toDocument) .hasValueSatisfying(it -> { assertThat(it.get("fields", List.class)).hasSize(2).contains(new Document("path", "spring") .append("bsonType", "long").append("queries", List.of()).append("keyId", BsonNull.VALUE)); @@ -127,7 +129,7 @@ class CollectionOptionsUnitTests { @SuppressWarnings("unchecked") void queryableEncryptionPropertiesOverridesPathFromSchema() { - EncryptedCollectionOptions encryptionOptions = EncryptedCollectionOptions.fromSchema(MongoJsonSchema.builder() + EncryptedFieldsOptions encryptionOptions = EncryptedFieldsOptions.fromSchema(MongoJsonSchema.builder() .property(queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int32("spring")), List.of())) .property(queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int64("data")), List.of())).build()); @@ -135,18 +137,32 @@ class CollectionOptionsUnitTests { CollectionOptions collectionOptions = CollectionOptions.encryptedCollection( encryptionOptions.queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int64("spring")))); - assertThat(collectionOptions.getEncryptionOptions()).map(EncryptedCollectionOptions::toDocument) + assertThat(collectionOptions.getEncryptedFieldsOptions()).map(EncryptedFieldsOptions::toDocument) .hasValueSatisfying(it -> { assertThat(it.get("fields", List.class)).hasSize(2).contains(new Document("path", "spring") .append("bsonType", "long").append("queries", List.of()).append("keyId", BsonNull.VALUE)); }); } + @Test // GH-4185 + void encryptionOptionsAreImmutable() { + + EncryptedFieldsOptions source = EncryptedFieldsOptions + .fromProperties(List.of(queryable(int32("spring.data"), List.of(QueryCharacteristics.range().min(1))))); + + assertThat(source.queryable(queryable(int32("mongodb"), List.of(QueryCharacteristics.range().min(1))))) + .isNotSameAs(source).satisfies(it -> { + assertThat(it.toDocument().get("fields", List.class)).hasSize(2); + }); + + assertThat(source.toDocument().get("fields", List.class)).hasSize(1); + } + @Test // GH-4185 @SuppressWarnings("unchecked") void queryableEncryptionPropertiesOverridesNestedPathFromSchema() { - EncryptedCollectionOptions encryptionOptions = EncryptedCollectionOptions.fromSchema(MongoJsonSchema.builder() + EncryptedFieldsOptions encryptionOptions = EncryptedFieldsOptions.fromSchema(MongoJsonSchema.builder() .property(JsonSchemaProperty.object("spring") .properties(queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int32("data")), List.of()))) .property(queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int64("mongodb")), List.of())).build()); @@ -155,7 +171,7 @@ class CollectionOptionsUnitTests { CollectionOptions collectionOptions = CollectionOptions.encryptedCollection( encryptionOptions.queryable(JsonSchemaProperty.encrypted(JsonSchemaProperty.int64("spring.data")))); - assertThat(collectionOptions.getEncryptionOptions()).map(EncryptedCollectionOptions::toDocument) + assertThat(collectionOptions.getEncryptedFieldsOptions()).map(EncryptedFieldsOptions::toDocument) .hasValueSatisfying(it -> { assertThat(it.get("fields", List.class)).hasSize(2).contains(new Document("path", "spring.data") .append("bsonType", "long").append("queries", List.of()).append("keyId", BsonNull.VALUE)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java index a4106b1ce..4cfb84087 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java @@ -40,7 +40,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.data.convert.PropertyValueConverterFactory; import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; import org.springframework.data.mongodb.core.CollectionOptions; -import org.springframework.data.mongodb.core.CollectionOptions.EncryptedCollectionOptions; +import org.springframework.data.mongodb.core.CollectionOptions.EncryptedFieldsOptions; import org.springframework.data.mongodb.core.MongoJsonSchemaCreator; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter; @@ -168,8 +168,8 @@ class RangeEncryptionTests { .createSchemaFor(Person.class); // Document encryptedFields = CollectionOptions.encryptedCollection(personSchema) // - .getEncryptionOptions() // - .map(EncryptedCollectionOptions::toDocument) // + .getEncryptedFieldsOptions() // + .map(EncryptedFieldsOptions::toDocument) // .orElseThrow(); CreateCollectionOptions createCollectionOptions = new CreateCollectionOptions()