committed by
Christoph Strobl
parent
5235aabf29
commit
e89548a846
@@ -24,12 +24,12 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.BsonBinary;
|
||||
import org.bson.BsonBinarySubType;
|
||||
import org.bson.BsonNull;
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty;
|
||||
@@ -43,7 +43,6 @@ import org.springframework.data.mongodb.core.validation.Validator;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.lang.CheckReturnValue;
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -241,11 +240,11 @@ public class CollectionOptions {
|
||||
* Create new {@link CollectionOptions} with already given settings and {@code validationOptions} set to given
|
||||
* {@link MongoJsonSchema}.
|
||||
*
|
||||
* @param schema can be {@literal null}.
|
||||
* @param schema must not be {@literal null}.
|
||||
* @return new {@link CollectionOptions}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public CollectionOptions schema(@Nullable MongoJsonSchema schema) {
|
||||
public CollectionOptions schema(MongoJsonSchema schema) {
|
||||
return validator(Validator.schema(schema));
|
||||
}
|
||||
|
||||
@@ -473,7 +472,7 @@ public class CollectionOptions {
|
||||
* Get the {@code encryptedFields} if available.
|
||||
*
|
||||
* @return {@link Optional#empty()} if not specified.
|
||||
* @since 4.5.0
|
||||
* @since 4.5
|
||||
*/
|
||||
public Optional<EncryptedFieldsOptions> getEncryptedFieldsOptions() {
|
||||
return Optional.ofNullable(encryptedFieldsOptions);
|
||||
@@ -552,7 +551,8 @@ public class CollectionOptions {
|
||||
private final @Nullable ValidationLevel validationLevel;
|
||||
private final @Nullable ValidationAction validationAction;
|
||||
|
||||
public ValidationOptions(Validator validator, ValidationLevel validationLevel, ValidationAction validationAction) {
|
||||
public ValidationOptions(@Nullable Validator validator, @Nullable ValidationLevel validationLevel,
|
||||
@Nullable ValidationAction validationAction) {
|
||||
|
||||
this.validator = validator;
|
||||
this.validationLevel = validationLevel;
|
||||
@@ -669,7 +669,7 @@ public class CollectionOptions {
|
||||
|
||||
/**
|
||||
* Encapsulation of Encryption options for collections.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 4.5
|
||||
*/
|
||||
@@ -677,8 +677,19 @@ public class CollectionOptions {
|
||||
|
||||
private static final EncryptedFieldsOptions NONE = new EncryptedFieldsOptions();
|
||||
|
||||
private @Nullable MongoJsonSchema schema;
|
||||
private List<QueryableJsonSchemaProperty> queryableProperties;
|
||||
private final @Nullable MongoJsonSchema schema;
|
||||
private final List<QueryableJsonSchemaProperty> queryableProperties;
|
||||
|
||||
EncryptedFieldsOptions() {
|
||||
this(null, List.of());
|
||||
}
|
||||
|
||||
private EncryptedFieldsOptions(@Nullable MongoJsonSchema schema,
|
||||
List<QueryableJsonSchemaProperty> queryableProperties) {
|
||||
|
||||
this.schema = schema;
|
||||
this.queryableProperties = queryableProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link EncryptedFieldsOptions#NONE}
|
||||
@@ -701,17 +712,6 @@ public class CollectionOptions {
|
||||
return new EncryptedFieldsOptions(null, List.copyOf(properties));
|
||||
}
|
||||
|
||||
EncryptedFieldsOptions() {
|
||||
this(null, List.of());
|
||||
}
|
||||
|
||||
private EncryptedFieldsOptions(@Nullable MongoJsonSchema schema,
|
||||
List<QueryableJsonSchemaProperty> queryableProperties) {
|
||||
|
||||
this.schema = schema;
|
||||
this.queryableProperties = queryableProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new {@link QueryableJsonSchemaProperty queryable property} for the given source property.
|
||||
* <p>
|
||||
@@ -739,7 +739,6 @@ public class CollectionOptions {
|
||||
return new Document("fields", selectPaths());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Document> selectPaths() {
|
||||
|
||||
Map<String, Document> fields = new LinkedHashMap<>();
|
||||
@@ -760,10 +759,13 @@ public class CollectionOptions {
|
||||
|
||||
List<Document> converted = new ArrayList<>(queryableProperties.size());
|
||||
for (QueryableJsonSchemaProperty property : queryableProperties) {
|
||||
|
||||
Document field = new Document("path", property.getIdentifier());
|
||||
|
||||
if (!property.getTypes().isEmpty()) {
|
||||
field.append("bsonType", property.getTypes().iterator().next().toBsonType().value());
|
||||
}
|
||||
|
||||
if (property
|
||||
.getTargetProperty() instanceof IdentifiableJsonSchemaProperty.EncryptedJsonSchemaProperty encrypted) {
|
||||
if (encrypted.getKeyId() != null) {
|
||||
@@ -775,11 +777,13 @@ public class CollectionOptions {
|
||||
}
|
||||
}
|
||||
}
|
||||
field.append("queries", property.getCharacteristics().getCharacteristics().stream()
|
||||
.map(QueryCharacteristic::toDocument).collect(Collectors.toList()));
|
||||
|
||||
field.append("queries", property.getCharacteristics().map(QueryCharacteristic::toDocument).toList());
|
||||
|
||||
if (!field.containsKey("keyId")) {
|
||||
field.append("keyId", BsonNull.VALUE);
|
||||
}
|
||||
|
||||
converted.add(field);
|
||||
}
|
||||
return converted;
|
||||
@@ -813,7 +817,7 @@ public class CollectionOptions {
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectPaths(Document document, String currentPath, Map<String, Document> paths) {
|
||||
private static void collectPaths(Document document, @Nullable String currentPath, Map<String, Document> paths) {
|
||||
|
||||
if (document.containsKey("type") && document.get("type").equals("object")) {
|
||||
Object o = document.get("properties");
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.bson.BsonNull;
|
||||
import org.bson.Document;
|
||||
@@ -377,14 +378,16 @@ class EntityOperations {
|
||||
result.timeSeriesOptions(options);
|
||||
});
|
||||
|
||||
collectionOptions.getChangeStreamOptions().ifPresent(it -> result
|
||||
.changeStreamPreAndPostImagesOptions(new ChangeStreamPreAndPostImagesOptions(it.getPreAndPostImages())));
|
||||
collectionOptions.getChangeStreamOptions() //
|
||||
.map(CollectionOptions.CollectionChangeStreamOptions::getPreAndPostImages) //
|
||||
.map(ChangeStreamPreAndPostImagesOptions::new) //
|
||||
.ifPresent(result::changeStreamPreAndPostImagesOptions);
|
||||
|
||||
collectionOptions.getEncryptedFieldsOptions() //
|
||||
.map(EncryptedFieldsOptions::toDocument) //
|
||||
.filter(Predicate.not(Document::isEmpty)) //
|
||||
.ifPresent(result::encryptedFields);
|
||||
|
||||
collectionOptions.getEncryptedFieldsOptions().map(EncryptedFieldsOptions::toDocument).ifPresent(encryptedFields -> {
|
||||
if (!encryptedFields.isEmpty()) {
|
||||
result.encryptedFields(encryptedFields);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
@@ -32,7 +33,6 @@ import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.Queryable;
|
||||
import org.springframework.data.mongodb.core.mapping.RangeEncrypted;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ArrayJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.EncryptedJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ObjectJsonSchemaProperty;
|
||||
@@ -126,23 +126,9 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(type);
|
||||
MongoJsonSchemaBuilder schemaBuilder = MongoJsonSchema.builder();
|
||||
|
||||
{
|
||||
Encrypted encrypted = entity.findAnnotation(Encrypted.class);
|
||||
if (encrypted != null) {
|
||||
|
||||
Document encryptionMetadata = new Document();
|
||||
|
||||
Collection<Object> encryptionKeyIds = entity.getEncryptionKeyIds();
|
||||
if (!CollectionUtils.isEmpty(encryptionKeyIds)) {
|
||||
encryptionMetadata.append("keyId", encryptionKeyIds);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(encrypted.algorithm())) {
|
||||
encryptionMetadata.append("algorithm", encrypted.algorithm());
|
||||
}
|
||||
|
||||
schemaBuilder.encryptionMetadata(encryptionMetadata);
|
||||
}
|
||||
Encrypted encrypted = entity.findAnnotation(Encrypted.class);
|
||||
if (encrypted != null) {
|
||||
schemaBuilder.encryptionMetadata(getEncryptionMetadata(entity, encrypted));
|
||||
}
|
||||
|
||||
List<JsonSchemaProperty> schemaProperties = computePropertiesForEntity(Collections.emptyList(), entity);
|
||||
@@ -151,6 +137,22 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
return schemaBuilder.build();
|
||||
}
|
||||
|
||||
private static Document getEncryptionMetadata(MongoPersistentEntity<?> entity, Encrypted encrypted) {
|
||||
|
||||
Document encryptionMetadata = new Document();
|
||||
|
||||
Collection<Object> encryptionKeyIds = entity.getEncryptionKeyIds();
|
||||
if (!CollectionUtils.isEmpty(encryptionKeyIds)) {
|
||||
encryptionMetadata.append("keyId", encryptionKeyIds);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(encrypted.algorithm())) {
|
||||
encryptionMetadata.append("algorithm", encrypted.algorithm());
|
||||
}
|
||||
|
||||
return encryptionMetadata;
|
||||
}
|
||||
|
||||
private List<JsonSchemaProperty> computePropertiesForEntity(List<MongoPersistentProperty> path,
|
||||
MongoPersistentEntity<?> entity) {
|
||||
|
||||
@@ -190,8 +192,8 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
Class<?> rawTargetType = computeTargetType(property); // target type before conversion
|
||||
Class<?> targetType = converter.getTypeMapper().getWriteTargetTypeFor(rawTargetType); // conversion target type
|
||||
|
||||
|
||||
if ((rawTargetType.isPrimitive() || ClassUtils.isPrimitiveArray(rawTargetType)) && targetType == Object.class || ClassUtils.isAssignable(targetType, rawTargetType) ) {
|
||||
if ((rawTargetType.isPrimitive() || ClassUtils.isPrimitiveArray(rawTargetType)) && targetType == Object.class
|
||||
|| ClassUtils.isAssignable(targetType, rawTargetType)) {
|
||||
targetType = rawTargetType;
|
||||
}
|
||||
|
||||
@@ -317,14 +319,15 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
if (queryable.contentionFactor() >= 0) {
|
||||
options.put("contention", queryable.contentionFactor());
|
||||
}
|
||||
if (!queryable.queryAttributes().isEmpty()) {
|
||||
|
||||
if (StringUtils.hasText(queryable.queryAttributes())) {
|
||||
options.putAll(Document.parse(queryable.queryAttributes()));
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
};
|
||||
return new QueryableJsonSchemaProperty(enc, QueryCharacteristics.of(List.of(characteristic)));
|
||||
return new QueryableJsonSchemaProperty(enc, QueryCharacteristics.of(characteristic));
|
||||
}
|
||||
|
||||
private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<MongoPersistentProperty> path,
|
||||
|
||||
@@ -79,7 +79,6 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param operatorContext
|
||||
* @return new instance of {@link MongoConversionContext}.
|
||||
* @since 4.5
|
||||
@@ -119,71 +118,53 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
|
||||
/**
|
||||
* The {@link OperatorContext} provides access to the actual conversion intent like a write operation or a query
|
||||
* operator such as {@literal $gte}.
|
||||
*
|
||||
*
|
||||
* @since 4.5
|
||||
*/
|
||||
public interface OperatorContext {
|
||||
|
||||
/**
|
||||
* The operator the conversion is used in.
|
||||
*
|
||||
* @return {@literal write} for simple write operations during save, or a query operator.
|
||||
*/
|
||||
String getOperator();
|
||||
String operator();
|
||||
|
||||
/**
|
||||
* The context path the operator is used in.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
String getPath();
|
||||
String path();
|
||||
|
||||
boolean isWriteOperation();
|
||||
|
||||
}
|
||||
|
||||
public static class WriteOperatorContext implements OperatorContext {
|
||||
|
||||
private final String path;
|
||||
|
||||
public WriteOperatorContext(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
record WriteOperatorContext(String path) implements OperatorContext {
|
||||
|
||||
@Override
|
||||
public String getOperator() {
|
||||
public String operator() {
|
||||
return "write";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteOperation() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class QueryOperatorContext implements OperatorContext {
|
||||
|
||||
private final String operator;
|
||||
private final String path;
|
||||
record QueryOperatorContext(String operator, String path) implements OperatorContext {
|
||||
|
||||
public QueryOperatorContext(@Nullable String operator, String path) {
|
||||
this.operator = operator != null ? operator : "$eq";
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getOperator() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteOperation() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -711,7 +711,8 @@ public class QueryMapper {
|
||||
|
||||
return BsonUtils.mapValues(document, (key, val) -> {
|
||||
if (isKeyword(key)) {
|
||||
return convertValueWithConversionContext(documentField, val, val, valueConverter, conversionContext.forOperator(new QueryOperatorContext(key, conversionContext.getOperatorContext().getPath())));
|
||||
return convertValueWithConversionContext(documentField, val, val, valueConverter, conversionContext
|
||||
.forOperator(new QueryOperatorContext(key, conversionContext.getOperatorContext().path())));
|
||||
}
|
||||
return val;
|
||||
});
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert.encryption;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.springframework.data.mongodb.core.encryption.EncryptionOptions.QueryableEncryptionOptions;
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.Collections.*;
|
||||
import static org.springframework.data.mongodb.core.encryption.EncryptionOptions.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -32,6 +32,7 @@ import org.bson.BsonDocument;
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.Binary;
|
||||
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConversionContext;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConversionContext.OperatorContext;
|
||||
@@ -59,8 +60,8 @@ import org.springframework.util.StringUtils;
|
||||
public class MongoEncryptionConverter implements EncryptingConverter<Object, Object> {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(MongoEncryptionConverter.class);
|
||||
private static final String EQUALITY_OPERATOR = "$eq";
|
||||
private static final List<String> RANGE_OPERATORS = asList("$gt", "$gte", "$lt", "$lte");
|
||||
public static final String AND_OPERATOR = "$and";
|
||||
|
||||
private final Encryption<BsonValue, BsonBinary> encryption;
|
||||
private final EncryptionKeyResolver keyResolver;
|
||||
@@ -189,7 +190,7 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
|
||||
}
|
||||
|
||||
private static @Nullable QueryableEncryptionOptions getEQOptions(MongoPersistentProperty persistentProperty,
|
||||
OperatorContext operatorContext) {
|
||||
@Nullable OperatorContext operatorContext) {
|
||||
|
||||
Queryable queryableAnnotation = persistentProperty.findAnnotation(Queryable.class);
|
||||
if (queryableAnnotation == null || !StringUtils.hasText(queryableAnnotation.queryType())) {
|
||||
@@ -248,29 +249,29 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
|
||||
* The mongodb-crypt {@code encryptExpression} has strict formatting requirements so this method ensures these
|
||||
* requirements are met and then picks out and returns just the value for use with a range query.
|
||||
*
|
||||
* @param fieldNameAndQueryOperator field name and query operator
|
||||
* @param value the value of the expression to be encrypted
|
||||
* @param encryptionOptions the options
|
||||
* @return the encrypted range value for use in a range query
|
||||
* @param operatorContext field name and query operator.
|
||||
* @param value the value of the expression to be encrypted.
|
||||
* @param encryptionOptions the options.
|
||||
* @return the encrypted range value for use in a range query.
|
||||
*/
|
||||
private BsonValue encryptExpression(OperatorContext operatorContext, Object value,
|
||||
EncryptionOptions encryptionOptions) {
|
||||
|
||||
BsonValue doc = BsonUtils.simpleToBsonValue(value);
|
||||
|
||||
String fieldName = operatorContext.getPath();
|
||||
String queryOperator = operatorContext.getOperator();
|
||||
String fieldName = operatorContext.path();
|
||||
String queryOperator = operatorContext.operator();
|
||||
|
||||
if (!RANGE_OPERATORS.contains(queryOperator)) {
|
||||
throw new AssertionError(String.format("Not a valid range query. Querying a range encrypted field but the "
|
||||
+ "query operator '%s' for field path '%s' is not a range query.", queryOperator, fieldName));
|
||||
}
|
||||
|
||||
BsonDocument encryptExpression = new BsonDocument("$and",
|
||||
BsonDocument encryptExpression = new BsonDocument(AND_OPERATOR,
|
||||
new BsonArray(singletonList(new BsonDocument(fieldName, new BsonDocument(queryOperator, doc)))));
|
||||
|
||||
BsonDocument result = encryption.encryptExpression(encryptExpression, encryptionOptions);
|
||||
return result.getArray("$and").get(0).asDocument().getDocument(fieldName).getBinary(queryOperator);
|
||||
return result.getArray(AND_OPERATOR).get(0).asDocument().getDocument(fieldName).getBinary(queryOperator);
|
||||
}
|
||||
|
||||
private BsonValue collectionLikeToBsonValue(Object value, MongoPersistentProperty property,
|
||||
|
||||
@@ -20,11 +20,13 @@ import org.bson.BsonDocument;
|
||||
/**
|
||||
* Component responsible for encrypting and decrypting values.
|
||||
*
|
||||
* @param <P> plaintext type.
|
||||
* @param <C> ciphertext type.
|
||||
* @author Christoph Strobl
|
||||
* @author Ross Lawley
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface Encryption<S, T> {
|
||||
public interface Encryption<P, C> {
|
||||
|
||||
/**
|
||||
* Encrypt the given value.
|
||||
@@ -33,7 +35,7 @@ public interface Encryption<S, T> {
|
||||
* @param options must not be {@literal null}.
|
||||
* @return the encrypted value.
|
||||
*/
|
||||
T encrypt(S value, EncryptionOptions options);
|
||||
C encrypt(P value, EncryptionOptions options);
|
||||
|
||||
/**
|
||||
* Decrypt the given value.
|
||||
@@ -41,7 +43,7 @@ public interface Encryption<S, T> {
|
||||
* @param value must not be {@literal null}.
|
||||
* @return the decrypted value.
|
||||
*/
|
||||
S decrypt(T value);
|
||||
P decrypt(C value);
|
||||
|
||||
/**
|
||||
* Encrypt the given expression.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025. the original author or authors.
|
||||
* Copyright 2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,13 +30,11 @@ public @interface Queryable {
|
||||
|
||||
/**
|
||||
* @return empty {@link String} if not set.
|
||||
* @since 4.5
|
||||
*/
|
||||
String queryType() default "";
|
||||
|
||||
/**
|
||||
* @return empty {@link String} if not set.
|
||||
* @since 4.5
|
||||
*/
|
||||
String queryAttributes() default "";
|
||||
|
||||
@@ -46,4 +44,5 @@ public @interface Queryable {
|
||||
* @return the contention factor
|
||||
*/
|
||||
long contentionFactor() default -1;
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
public @interface RangeEncrypted {
|
||||
|
||||
/**
|
||||
* Set the contention factor
|
||||
* Set the contention factor.
|
||||
*
|
||||
* @return the contention factor
|
||||
*/
|
||||
@@ -42,15 +42,16 @@ public @interface RangeEncrypted {
|
||||
long contentionFactor() default -1;
|
||||
|
||||
/**
|
||||
* Set the {@literal range} options
|
||||
* Set the {@literal range} options.
|
||||
* <p>
|
||||
* Should be valid extended json representing the range options and including the following values: {@code min},
|
||||
* {@code max}, {@code trimFactor} and {@code sparsity}.
|
||||
* Should be valid extended {@link org.bson.Document#parse(String) JSON} representing the range options and including
|
||||
* the following values: {@code min}, {@code max}, {@code trimFactor} and {@code sparsity}.
|
||||
* <p>
|
||||
* Please note that values are data type sensitive and may require proper identification via eg. {@code $numberLong}.
|
||||
*
|
||||
* @return the json representation of range options
|
||||
* @return the {@link org.bson.Document#parse(String) JSON} representation of range options.
|
||||
*/
|
||||
@AliasFor(annotation = Queryable.class, value = "queryAttributes")
|
||||
String rangeOptions() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -16,21 +16,25 @@
|
||||
package org.springframework.data.mongodb.core.schema;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.BsonNull;
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 4.5
|
||||
*/
|
||||
public class QueryCharacteristics {
|
||||
public class QueryCharacteristics implements Streamable<QueryCharacteristic> {
|
||||
|
||||
private static final QueryCharacteristics NONE = new QueryCharacteristics(List.of());
|
||||
private static final QueryCharacteristics NONE = new QueryCharacteristics(Collections.emptyList());
|
||||
|
||||
private final List<QueryCharacteristic> characteristics;
|
||||
|
||||
@@ -46,14 +50,19 @@ public class QueryCharacteristics {
|
||||
return new QueryCharacteristics(List.copyOf(characteristics));
|
||||
}
|
||||
|
||||
QueryCharacteristics(QueryCharacteristic... characteristics) {
|
||||
this.characteristics = Arrays.asList(characteristics);
|
||||
public static QueryCharacteristics of(QueryCharacteristic... characteristics) {
|
||||
return new QueryCharacteristics(Arrays.asList(characteristics));
|
||||
}
|
||||
|
||||
public List<QueryCharacteristic> getCharacteristics() {
|
||||
return characteristics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<QueryCharacteristic> iterator() {
|
||||
return this.characteristics.iterator();
|
||||
}
|
||||
|
||||
public static <T> RangeQuery<T> range() {
|
||||
return new RangeQuery<>();
|
||||
}
|
||||
@@ -96,7 +105,8 @@ public class QueryCharacteristics {
|
||||
this(Range.unbounded(), null, null, null);
|
||||
}
|
||||
|
||||
public RangeQuery(Range<T> valueRange, Integer trimFactor, Long sparsity, Long contention) {
|
||||
public RangeQuery(@Nullable Range<T> valueRange, @Nullable Integer trimFactor, @Nullable Long sparsity,
|
||||
@Nullable Long contention) {
|
||||
this.valueRange = valueRange;
|
||||
this.trimFactor = trimFactor;
|
||||
this.sparsity = sparsity;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
* Copyright 2024-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.encryption;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -39,6 +38,7 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -369,7 +369,7 @@ class RangeEncryptionTests {
|
||||
path = StringUtils.arrayToDelimitedString(ctx.getProperty().getMongoField().getName().parts(), ".");
|
||||
}
|
||||
if (ctx.getOperatorContext() != null) {
|
||||
path = ctx.getOperatorContext().getPath();
|
||||
path = ctx.getOperatorContext().path();
|
||||
}
|
||||
return EncryptionKey.keyId(keyHolder.getEncryptionKey(path));
|
||||
}));
|
||||
|
||||
@@ -15,13 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.schema;
|
||||
|
||||
import static org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.EncryptedJsonSchemaProperty.rangeEncrypted;
|
||||
import static org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.EncryptedJsonSchemaProperty.*;
|
||||
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.*;
|
||||
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.encrypted;
|
||||
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.number;
|
||||
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.queryable;
|
||||
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.string;
|
||||
import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
|
||||
import static org.springframework.data.mongodb.test.util.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.springframework.data.mongodb.test.util.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -119,13 +116,27 @@ class MongoJsonSchemaUnitTests {
|
||||
List.of(QueryCharacteristics.range().contention(0).trimFactor(1).sparsity(1).min(0).max(200))))
|
||||
.build();
|
||||
|
||||
assertThat(schema.toDocument()).isEqualTo(new Document("$jsonSchema",
|
||||
new Document("type", "object").append("properties",
|
||||
new Document("ssn",
|
||||
new Document("encrypt",
|
||||
new Document("bsonType", "long").append("algorithm", "Range").append("queries",
|
||||
List.of(new Document("contention", 0L).append("trimFactor", 1).append("sparsity", 1L)
|
||||
.append("queryType", "range").append("min", 0).append("max", 200))))))));
|
||||
assertThat(schema.toDocument().get("$jsonSchema", Document.class)).isEqualTo("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ssn": {
|
||||
"encrypt": {
|
||||
"bsonType": "long",
|
||||
"algorithm": "Range",
|
||||
"queries": [{
|
||||
"queryType": "range",
|
||||
"contention": {$numberLong: "0"},
|
||||
"trimFactor": 1,
|
||||
"sparsity": {$numberLong: "1"},
|
||||
"min": 0,
|
||||
"max": 200
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1835
|
||||
|
||||
Reference in New Issue
Block a user