@ -24,13 +24,13 @@ import java.util.Map;
@@ -24,13 +24,13 @@ import java.util.Map;
import java.util.Map.Entry ;
import java.util.Optional ;
import java.util.function.Function ;
import java.util.stream.StreamSupport ;
import org.bson.BsonBinary ;
import org.bson.BsonBinarySubType ;
import org.bson.BsonNull ;
import org.bson.Document ;
import org.jspecify.annotations.Nullable ;
import org.springframework.data.mongodb.core.mapping.Field ;
import org.springframework.data.mongodb.core.query.Collation ;
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty ;
@ -391,6 +391,7 @@ public class CollectionOptions {
@@ -391,6 +391,7 @@ public class CollectionOptions {
*
* @param encryptedFieldsOptions must not be { @literal null } .
* @return new instance of { @link CollectionOptions } .
* @since 4 . 5
* /
@Contract ( "_ -> new" )
@CheckReturnValue
@ -711,7 +712,7 @@ public class CollectionOptions {
@@ -711,7 +712,7 @@ public class CollectionOptions {
/ * *
* @return new instance of { @link EncryptedFieldsOptions } .
* /
public static EncryptedFieldsOptions fromProperties ( List < JsonSchemaProperty > properties ) {
public static EncryptedFieldsOptions fromProperties ( List < ? extends JsonSchemaProperty > properties ) {
return new EncryptedFieldsOptions ( null , List . copyOf ( properties ) ) ;
}
@ -743,6 +744,7 @@ public class CollectionOptions {
@@ -743,6 +744,7 @@ public class CollectionOptions {
*
* @param property must not be { @literal null } .
* @return new instance of { @link EncryptedFieldsOptions } .
* @since 4 . 5 . 1
* /
@Contract ( "_ -> new" )
@CheckReturnValue
@ -751,24 +753,26 @@ public class CollectionOptions {
@@ -751,24 +753,26 @@ public class CollectionOptions {
}
/ * *
* Add a { @link JsonSchemaProperty property } that should not be encrypted but not queryable .
* Add a { @link JsonSchemaProperty property } that should be encrypted but not queryable .
*
* @param property must not be { @literal null } .
* @param key can be { @literal null } .
* @param keyId the key identifier to be used , can be { @literal null } .
* @return new instance of { @link EncryptedFieldsOptions } .
* @since 4 . 5 . 1
* /
@Contract ( "_, _ -> new" )
@CheckReturnValue
public EncryptedFieldsOptions encrypted ( JsonSchemaProperty property , @Nullable Object key ) {
public EncryptedFieldsOptions encrypted ( JsonSchemaProperty property , @Nullable Object keyId ) {
List < JsonSchemaProperty > targetPropertyList = new ArrayList < > ( properties . size ( ) + 1 ) ;
targetPropertyList . addAll ( properties ) ;
if ( property instanceof IdentifiableJsonSchemaProperty . EncryptedJsonSchemaProperty ) {
targetPropertyList . add ( property ) ;
} else {
EncryptedJsonSchemaProperty encryptedJsonSchemaProperty = new EncryptedJsonSchemaProperty ( property ) ;
if ( key ! = null ) {
targetPropertyList . add ( encryptedJsonSchemaProperty . keyId ( key ) ) ;
if ( keyId ! = null ) {
targetPropertyList . add ( encryptedJsonSchemaProperty . keyId ( keyId ) ) ;
}
}
@ -799,45 +803,48 @@ public class CollectionOptions {
@@ -799,45 +803,48 @@ public class CollectionOptions {
List < Document > converted = new ArrayList < > ( properties . size ( ) ) ;
for ( JsonSchemaProperty property : properties ) {
converted . add ( getEncryptedField ( property ) ) ;
}
return converted ;
}
Document field = new Document ( "path" , property . getIdentifier ( ) ) ;
private Document getEncryptedField ( JsonSchemaProperty property ) {
if ( ! property . getTypes ( ) . isEmpty ( ) ) {
field . append ( "bsonType" , property . getTypes ( ) . iterator ( ) . next ( ) . toBsonType ( ) . value ( ) ) ;
}
Document field = new Document ( "path" , property . getIdentifier ( ) ) ;
if ( property instanceof QueryableJsonSchemaProperty qproperty & & qproperty
. getTargetProperty ( ) instanceof IdentifiableJsonSchemaProperty . EncryptedJsonSchemaProperty encrypted ) {
if ( encrypted . getKeyId ( ) ! = null ) {
if ( encrypted . getKeyId ( ) instanceof String stringKey ) {
field . append ( "keyId" ,
new BsonBinary ( BsonBinarySubType . UUID_STANDARD , stringKey . getBytes ( StandardCharsets . UTF_8 ) ) ) ;
} else {
field . append ( "keyId" , encrypted . getKeyId ( ) ) ;
}
}
} else if ( property instanceof IdentifiableJsonSchemaProperty . EncryptedJsonSchemaProperty encrypted ) {
if ( encrypted . getKeyId ( ) ! = null ) {
if ( encrypted . getKeyId ( ) instanceof String stringKey ) {
field . append ( "keyId" ,
new BsonBinary ( BsonBinarySubType . UUID_STANDARD , stringKey . getBytes ( StandardCharsets . UTF_8 ) ) ) ;
} else {
field . append ( "keyId" , encrypted . getKeyId ( ) ) ;
}
}
}
if ( ! property . getTypes ( ) . isEmpty ( ) ) {
field . append ( "bsonType" , property . getTypes ( ) . iterator ( ) . next ( ) . toBsonType ( ) . value ( ) ) ;
}
if ( property instanceof QueryableJsonSchemaProperty qp
& & qp . getTargetProperty ( ) instanceof IdentifiableJsonSchemaProperty . EncryptedJsonSchemaProperty encrypted
& & encrypted . getKeyId ( ) ! = null ) {
if ( property instanceof QueryableJsonSchemaProperty qproperty ) {
field . append ( "queries" , StreamSupport . stream ( qproperty . getCharacteristics ( ) . spliterator ( ) , false )
. map ( QueryCharacteristic : : toDocument ) . toList ( ) ) ;
if ( encrypted . getKeyId ( ) instanceof String stringKey ) {
field . append ( "keyId" ,
new BsonBinary ( BsonBinarySubType . UUID_STANDARD , stringKey . getBytes ( StandardCharsets . UTF_8 ) ) ) ;
} else {
field . append ( "keyId" , encrypted . getKeyId ( ) ) ;
}
if ( ! field . containsKey ( "keyId" ) ) {
field . append ( "keyId" , BsonNull . VALUE ) ;
} else if ( property instanceof IdentifiableJsonSchemaProperty . EncryptedJsonSchemaProperty encrypted
& & encrypted . getKeyId ( ) ! = null ) {
if ( encrypted . getKeyId ( ) instanceof String stringKey ) {
field . append ( "keyId" ,
new BsonBinary ( BsonBinarySubType . UUID_STANDARD , stringKey . getBytes ( StandardCharsets . UTF_8 ) ) ) ;
} else {
field . append ( "keyId" , encrypted . getKeyId ( ) ) ;
}
}
converted . add ( field ) ;
if ( property instanceof QueryableJsonSchemaProperty qp ) {
field . append ( "queries" , qp . getCharacteristics ( ) . map ( QueryCharacteristic : : toDocument ) . toList ( ) ) ;
}
return converted ;
if ( ! field . containsKey ( "keyId" ) ) {
field . append ( "keyId" , BsonNull . VALUE ) ;
}
return field ;
}
private List < Document > fromSchema ( ) {
@ -851,19 +858,25 @@ public class CollectionOptions {
@@ -851,19 +858,25 @@ public class CollectionOptions {
collectPaths ( root , null , paths ) ;
List < Document > fields = new ArrayList < > ( ) ;
if ( ! paths . isEmpty ( ) ) {
if ( paths . isEmpty ( ) ) {
return fields ;
}
for ( Entry < String , Document > entry : paths . entrySet ( ) ) {
Document field = new Document ( "path" , entry . getKey ( ) ) ;
field . append ( "keyId" , entry . getValue ( ) . getOrDefault ( "keyId" , BsonNull . VALUE ) ) ;
if ( entry . getValue ( ) . containsKey ( "bsonType" ) ) {
field . append ( "bsonType" , entry . getValue ( ) . get ( "bsonType" ) ) ;
}
if ( entry . getValue ( ) . containsKey ( "queries" ) ) {
field . put ( "queries" , entry . getValue ( ) . get ( "queries" ) ) ;
}
fields . add ( field ) ;
for ( Entry < String , Document > entry : paths . entrySet ( ) ) {
Document field = new Document ( "path" , entry . getKey ( ) ) ;
field . append ( "keyId" , entry . getValue ( ) . getOrDefault ( "keyId" , BsonNull . VALUE ) ) ;
if ( entry . getValue ( ) . containsKey ( "bsonType" ) ) {
field . append ( "bsonType" , entry . getValue ( ) . get ( "bsonType" ) ) ;
}
if ( entry . getValue ( ) . containsKey ( "queries" ) ) {
field . put ( "queries" , entry . getValue ( ) . get ( "queries" ) ) ;
}
fields . add ( field ) ;
}
return fields ;
@ -873,28 +886,29 @@ public class CollectionOptions {
@@ -873,28 +886,29 @@ public class CollectionOptions {
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" ) ;
if ( o = = null ) {
if ( ! ( o instanceof Document properties ) ) {
return ;
}
if ( o instanceof Document properties ) {
for ( Entry < String , Object > entry : properties . entrySet ( ) ) {
if ( entry . getValue ( ) instanceof Document nested ) {
String path = currentPath = = null ? entry . getKey ( ) : ( currentPath + "." + entry . getKey ( ) ) ;
if ( nested . containsKey ( "encrypt" ) ) {
Document target = new Document ( nested . get ( "encrypt" , Document . class ) ) ;
if ( nested . containsKey ( "queries" ) ) {
List < ? > queries = nested . get ( "queries" , List . class ) ;
if ( ! queries . isEmpty ( ) & & queries . iterator ( ) . next ( ) instanceof Document qd ) {
target . putAll ( qd ) ;
}
for ( Entry < String , Object > entry : properties . entrySet ( ) ) {
if ( entry . getValue ( ) instanceof Document nested ) {
String path = currentPath = = null ? entry . getKey ( ) : ( currentPath + "." + entry . getKey ( ) ) ;
if ( nested . containsKey ( "encrypt" ) ) {
Document target = new Document ( nested . get ( "encrypt" , Document . class ) ) ;
if ( nested . containsKey ( "queries" ) ) {
List < ? > queries = nested . get ( "queries" , List . class ) ;
if ( ! queries . isEmpty ( ) & & queries . iterator ( ) . next ( ) instanceof Document qd ) {
target . putAll ( qd ) ;
}
paths . put ( path , target ) ;
} else {
collectPaths ( nested , path , paths ) ;
}
paths . put ( path , target ) ;
} else {
collectPaths ( nested , path , paths ) ;
}
}
}