Browse Source

Introduce Queryable annotation

issue/4185-light
Christoph Strobl 9 months ago
parent
commit
7472a3abd8
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 15
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java
  2. 25
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java
  3. 12
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Encrypted.java
  4. 49
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Queryable.java
  5. 6
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/RangeEncrypted.java
  6. 41
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java

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

@ -31,6 +31,7 @@ import org.springframework.data.mongodb.core.mapping.Encrypted; @@ -31,6 +31,7 @@ import org.springframework.data.mongodb.core.mapping.Encrypted;
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;
@ -296,7 +297,8 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { @@ -296,7 +297,8 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
enc = enc.keys(property.getEncryptionKeyIds());
}
if (!StringUtils.hasText(encrypted.queryType())) {
Queryable queryable = property.findAnnotation(Queryable.class);
if (queryable == null || !StringUtils.hasText(queryable.queryType())) {
return enc;
}
@ -304,7 +306,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { @@ -304,7 +306,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
@Override
public String queryType() {
return encrypted.queryType();
return queryable.queryType();
}
@Override
@ -312,12 +314,11 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { @@ -312,12 +314,11 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
Document options = QueryCharacteristic.super.toDocument();
RangeEncrypted rangeEncrypted = property.findAnnotation(RangeEncrypted.class);
if (rangeEncrypted != null) {
options.put("contention", rangeEncrypted.contentionFactor());
if (queryable.contentionFactor() >= 0) {
options.put("contention", queryable.contentionFactor());
}
if (!encrypted.queryAttributes().isEmpty()) {
options.putAll(Document.parse(encrypted.queryAttributes()));
if (!queryable.queryAttributes().isEmpty()) {
options.putAll(Document.parse(queryable.queryAttributes()));
}
return options;

25
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java

@ -41,7 +41,7 @@ import org.springframework.data.mongodb.core.encryption.EncryptionKeyResolver; @@ -41,7 +41,7 @@ import org.springframework.data.mongodb.core.encryption.EncryptionKeyResolver;
import org.springframework.data.mongodb.core.encryption.EncryptionOptions;
import org.springframework.data.mongodb.core.mapping.Encrypted;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.RangeEncrypted;
import org.springframework.data.mongodb.core.mapping.Queryable;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
@ -179,7 +179,8 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj @@ -179,7 +179,8 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
EncryptionOptions encryptionOptions = new EncryptionOptions(algorithm, key,
getEQOptions(persistentProperty, fieldNameAndQueryOperator));
if (fieldNameAndQueryOperator != null && encryptionOptions.queryableEncryptionOptions() != null) {
if (fieldNameAndQueryOperator != null && encryptionOptions.queryableEncryptionOptions() != null
&& !encryptionOptions.queryableEncryptionOptions().getQueryType().equals("equality")) {
return encryptExpression(fieldNameAndQueryOperator, value, encryptionOptions);
} else {
return encryptValue(value, context, persistentProperty, encryptionOptions);
@ -189,28 +190,28 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj @@ -189,28 +190,28 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
private static @Nullable QueryableEncryptionOptions getEQOptions(MongoPersistentProperty persistentProperty,
String fieldNameAndQueryOperator) {
Encrypted encryptedAnnotation = persistentProperty.findAnnotation(Encrypted.class);
if (encryptedAnnotation != null && !StringUtils.hasText(encryptedAnnotation.queryType())) {
Queryable queryableAnnotation = persistentProperty.findAnnotation(Queryable.class);
if (queryableAnnotation == null || !StringUtils.hasText(queryableAnnotation.queryType())) {
return null;
}
QueryableEncryptionOptions queryableEncryptionOptions = QueryableEncryptionOptions.none();
String queryAttributes = encryptedAnnotation.queryAttributes();
String queryAttributes = queryableAnnotation.queryAttributes();
if (!queryAttributes.isEmpty()) {
queryableEncryptionOptions = queryableEncryptionOptions.attributes(Document.parse(queryAttributes));
}
RangeEncrypted rangeEncryptedAnnotation = persistentProperty.findAnnotation(RangeEncrypted.class);
if (rangeEncryptedAnnotation != null && rangeEncryptedAnnotation.contentionFactor() >= 0) {
queryableEncryptionOptions = queryableEncryptionOptions
.contentionFactor(rangeEncryptedAnnotation.contentionFactor());
if (queryableAnnotation.contentionFactor() >= 0) {
queryableEncryptionOptions = queryableEncryptionOptions.contentionFactor(queryableAnnotation.contentionFactor());
}
boolean isPartOfARangeQuery = fieldNameAndQueryOperator != null;
if (isPartOfARangeQuery || !encryptedAnnotation.queryType().isEmpty()) {
queryableEncryptionOptions = queryableEncryptionOptions.queryType(encryptedAnnotation.queryType()); // should the type move to an extra annotation?
queryableEncryptionOptions = queryableEncryptionOptions.contentionFactor(1l);
if (isPartOfARangeQuery) {
queryableEncryptionOptions = queryableEncryptionOptions.queryType(queryableAnnotation.queryType()); // should the
// type move
// to an extra
// annotation?
}
return queryableEncryptionOptions;
}

12
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Encrypted.java

@ -109,16 +109,4 @@ public @interface Encrypted { @@ -109,16 +109,4 @@ public @interface Encrypted {
* @see org.springframework.data.mongodb.core.EncryptionAlgorithms
*/
String algorithm() default "";
/**
* @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 "";
}

49
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Queryable.java

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.mapping;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Christoph Strobl
* @since 4.5
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
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 "";
/**
* Set the contention factor
*
* @return the contention factor
*/
long contentionFactor() default -1;
}

6
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/RangeEncrypted.java

@ -29,7 +29,8 @@ import org.springframework.core.annotation.AliasFor; @@ -29,7 +29,8 @@ import org.springframework.core.annotation.AliasFor;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Encrypted(algorithm = "Range", queryType = "range")
@Encrypted(algorithm = "Range")
@Queryable(queryType = "range")
public @interface RangeEncrypted {
/**
@ -37,6 +38,7 @@ public @interface RangeEncrypted { @@ -37,6 +38,7 @@ public @interface RangeEncrypted {
*
* @return the contention factor
*/
@AliasFor(annotation = Queryable.class, value = "contentionFactor")
long contentionFactor() default -1;
/**
@ -49,6 +51,6 @@ public @interface RangeEncrypted { @@ -49,6 +51,6 @@ public @interface RangeEncrypted {
*
* @return the json representation of range options
*/
@AliasFor(annotation = Encrypted.class, value = "queryAttributes")
@AliasFor(annotation = Queryable.class, value = "queryAttributes")
String rangeOptions() default "";
}

41
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java

@ -51,6 +51,7 @@ import org.springframework.data.mongodb.core.convert.MongoCustomConversions.Mong @@ -51,6 +51,7 @@ import org.springframework.data.mongodb.core.convert.MongoCustomConversions.Mong
import org.springframework.data.mongodb.core.convert.encryption.MongoEncryptionConverter;
import org.springframework.data.mongodb.core.mapping.Encrypted;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.Queryable;
import org.springframework.data.mongodb.core.mapping.RangeEncrypted;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
@ -110,12 +111,22 @@ class RangeEncryptionTests { @@ -110,12 +111,22 @@ class RangeEncryptionTests {
.rangeOptions(new RangeOptions().min(new BsonInt32(0)).max(new BsonInt32(200)))
.keyId(keyHolder.getEncryptionKey("encryptedInt")).queryType("range");
EncryptOptions stringEncOptions = new EncryptOptions("Range")
.contentionFactor(8L).rangeOptions(new RangeOptions())
.keyId(keyHolder.getEncryptionKey("name")).queryType("equality");
/*
@Encrypted(algorithm = "Indexed")
@Queryable(queryType = "equality", contentionFactor = 0)
*/
EncryptOptions equalityEncOptions = new EncryptOptions("Indexed")
.contentionFactor(0L)
.keyId(keyHolder.getEncryptionKey("age"));;
EncryptOptions equalityEncOptionsString = new EncryptOptions("Indexed")
.contentionFactor(0L)
.keyId(keyHolder.getEncryptionKey("name"));;
Document source = new Document("_id", "id-1");
source.put("name", clientEncryption.getClientEncryption().encrypt(new BsonString("It's a Me, Mario!"), stringEncOptions));
source.put("name", clientEncryption.getClientEncryption().encrypt(new BsonString("It's a Me, Mario!"), equalityEncOptionsString));
source.put("age", clientEncryption.getClientEncryption().encrypt(new BsonInt32(101), equalityEncOptions));
source.put("encryptedInt", clientEncryption.getClientEncryption().encrypt(new BsonInt32(101), encryptOptions));
source.put("_class", Person.class.getName());
@ -143,6 +154,16 @@ class RangeEncryptionTests { @@ -143,6 +154,16 @@ class RangeEncryptionTests {
assertThat(loaded).isEqualTo(source);
}
@Test
void eqQueryWorksOnEncryptedField() {
Person source = createPerson();
template.insert(source);
Person loaded = template.query(Person.class).matching(where("age").is(source.age)).firstValue();
assertThat(loaded).isEqualTo(source);
}
@Test
void canRangeMatchRangeEncryptedField() {
@ -200,7 +221,8 @@ class RangeEncryptionTests { @@ -200,7 +221,8 @@ class RangeEncryptionTests {
private Person createPerson() {
Person source = new Person();
source.id = "id-1";
source.name = "it'se me mario!";
source.name = "it's a me mario!";
source.age = 42;
source.encryptedInt = 101;
source.encryptedLong = 1001L;
return source;
@ -358,10 +380,17 @@ class RangeEncryptionTests { @@ -358,10 +380,17 @@ class RangeEncryptionTests {
static class Person {
String id;
@ValueConverter(MongoEncryptionConverter.class)
@Encrypted(algorithm = "Range", queryType = "equality")
@Encrypted(algorithm = "Indexed")
@Queryable(queryType = "equality", contentionFactor = 0)
String name;
@ValueConverter(MongoEncryptionConverter.class)
@Encrypted(algorithm = "Indexed")
@Queryable(queryType = "equality", contentionFactor = 0)
Integer age;
@ValueConverter(MongoEncryptionConverter.class)
@RangeEncrypted(contentionFactor = 0L,
rangeOptions = "{\"min\": 0, \"max\": 200, \"trimFactor\": 1, \"sparsity\": 1}") //

Loading…
Cancel
Save