2424import java .util .Map .Entry ;
2525import java .util .Optional ;
2626import java .util .function .Function ;
27- import java .util .stream .StreamSupport ;
2827
2928import org .bson .BsonBinary ;
3029import org .bson .BsonBinarySubType ;
3130import org .bson .BsonNull ;
3231import org .bson .Document ;
3332import org .jspecify .annotations .Nullable ;
33+
3434import org .springframework .data .mongodb .core .mapping .Field ;
3535import org .springframework .data .mongodb .core .query .Collation ;
3636import org .springframework .data .mongodb .core .schema .IdentifiableJsonSchemaProperty ;
@@ -391,6 +391,7 @@ public CollectionOptions changeStream(CollectionChangeStreamOptions changeStream
391391 *
392392 * @param encryptedFieldsOptions must not be {@literal null}.
393393 * @return new instance of {@link CollectionOptions}.
394+ * @since 4.5
394395 */
395396 @ Contract ("_ -> new" )
396397 @ CheckReturnValue
@@ -711,7 +712,7 @@ public static EncryptedFieldsOptions fromSchema(MongoJsonSchema schema) {
711712 /**
712713 * @return new instance of {@link EncryptedFieldsOptions}.
713714 */
714- public static EncryptedFieldsOptions fromProperties (List <JsonSchemaProperty > properties ) {
715+ public static EncryptedFieldsOptions fromProperties (List <? extends JsonSchemaProperty > properties ) {
715716 return new EncryptedFieldsOptions (null , List .copyOf (properties ));
716717 }
717718
@@ -743,6 +744,7 @@ public EncryptedFieldsOptions queryable(JsonSchemaProperty property, QueryCharac
743744 *
744745 * @param property must not be {@literal null}.
745746 * @return new instance of {@link EncryptedFieldsOptions}.
747+ * @since 4.5.1
746748 */
747749 @ Contract ("_ -> new" )
748750 @ CheckReturnValue
@@ -751,24 +753,26 @@ public EncryptedFieldsOptions with(EncryptedJsonSchemaProperty property) {
751753 }
752754
753755 /**
754- * Add a {@link JsonSchemaProperty property} that should not be encrypted but not queryable.
756+ * Add a {@link JsonSchemaProperty property} that should be encrypted but not queryable.
755757 *
756758 * @param property must not be {@literal null}.
757- * @param key can be {@literal null}.
759+ * @param keyId the key identifier to be used, can be {@literal null}.
758760 * @return new instance of {@link EncryptedFieldsOptions}.
761+ * @since 4.5.1
759762 */
760763 @ Contract ("_, _ -> new" )
761764 @ CheckReturnValue
762- public EncryptedFieldsOptions encrypted (JsonSchemaProperty property , @ Nullable Object key ) {
765+ public EncryptedFieldsOptions encrypted (JsonSchemaProperty property , @ Nullable Object keyId ) {
763766
764767 List <JsonSchemaProperty > targetPropertyList = new ArrayList <>(properties .size () + 1 );
765768 targetPropertyList .addAll (properties );
769+
766770 if (property instanceof IdentifiableJsonSchemaProperty .EncryptedJsonSchemaProperty ) {
767771 targetPropertyList .add (property );
768772 } else {
769773 EncryptedJsonSchemaProperty encryptedJsonSchemaProperty = new EncryptedJsonSchemaProperty (property );
770- if (key != null ) {
771- targetPropertyList .add (encryptedJsonSchemaProperty .keyId (key ));
774+ if (keyId != null ) {
775+ targetPropertyList .add (encryptedJsonSchemaProperty .keyId (keyId ));
772776 }
773777 }
774778
@@ -799,45 +803,48 @@ private List<Document> fromProperties() {
799803
800804 List <Document > converted = new ArrayList <>(properties .size ());
801805 for (JsonSchemaProperty property : properties ) {
806+ converted .add (getEncryptedField (property ));
807+ }
808+ return converted ;
809+ }
802810
803- Document field = new Document ( "path" , property . getIdentifier ());
811+ private Document getEncryptedField ( JsonSchemaProperty property ) {
804812
805- if (!property .getTypes ().isEmpty ()) {
806- field .append ("bsonType" , property .getTypes ().iterator ().next ().toBsonType ().value ());
807- }
813+ Document field = new Document ("path" , property .getIdentifier ());
808814
809- if (property instanceof QueryableJsonSchemaProperty qproperty && qproperty
810- .getTargetProperty () instanceof IdentifiableJsonSchemaProperty .EncryptedJsonSchemaProperty encrypted ) {
811- if (encrypted .getKeyId () != null ) {
812- if (encrypted .getKeyId () instanceof String stringKey ) {
813- field .append ("keyId" ,
814- new BsonBinary (BsonBinarySubType .UUID_STANDARD , stringKey .getBytes (StandardCharsets .UTF_8 )));
815- } else {
816- field .append ("keyId" , encrypted .getKeyId ());
817- }
818- }
819- } else if (property instanceof IdentifiableJsonSchemaProperty .EncryptedJsonSchemaProperty encrypted ) {
820- if (encrypted .getKeyId () != null ) {
821- if (encrypted .getKeyId () instanceof String stringKey ) {
822- field .append ("keyId" ,
823- new BsonBinary (BsonBinarySubType .UUID_STANDARD , stringKey .getBytes (StandardCharsets .UTF_8 )));
824- } else {
825- field .append ("keyId" , encrypted .getKeyId ());
826- }
827- }
828- }
815+ if (!property .getTypes ().isEmpty ()) {
816+ field .append ("bsonType" , property .getTypes ().iterator ().next ().toBsonType ().value ());
817+ }
818+
819+ if (property instanceof QueryableJsonSchemaProperty qp
820+ && qp .getTargetProperty () instanceof IdentifiableJsonSchemaProperty .EncryptedJsonSchemaProperty encrypted
821+ && encrypted .getKeyId () != null ) {
829822
830- if (property instanceof QueryableJsonSchemaProperty qproperty ) {
831- field .append ("queries" , StreamSupport .stream (qproperty .getCharacteristics ().spliterator (), false )
832- .map (QueryCharacteristic ::toDocument ).toList ());
823+ if (encrypted .getKeyId () instanceof String stringKey ) {
824+ field .append ("keyId" ,
825+ new BsonBinary (BsonBinarySubType .UUID_STANDARD , stringKey .getBytes (StandardCharsets .UTF_8 )));
826+ } else {
827+ field .append ("keyId" , encrypted .getKeyId ());
833828 }
834- if (!field .containsKey ("keyId" )) {
835- field .append ("keyId" , BsonNull .VALUE );
829+ } else if (property instanceof IdentifiableJsonSchemaProperty .EncryptedJsonSchemaProperty encrypted
830+ && encrypted .getKeyId () != null ) {
831+ if (encrypted .getKeyId () instanceof String stringKey ) {
832+ field .append ("keyId" ,
833+ new BsonBinary (BsonBinarySubType .UUID_STANDARD , stringKey .getBytes (StandardCharsets .UTF_8 )));
834+ } else {
835+ field .append ("keyId" , encrypted .getKeyId ());
836836 }
837+ }
837838
838- converted .add (field );
839+ if (property instanceof QueryableJsonSchemaProperty qp ) {
840+ field .append ("queries" , qp .getCharacteristics ().map (QueryCharacteristic ::toDocument ).toList ());
839841 }
840- return converted ;
842+
843+ if (!field .containsKey ("keyId" )) {
844+ field .append ("keyId" , BsonNull .VALUE );
845+ }
846+
847+ return field ;
841848 }
842849
843850 private List <Document > fromSchema () {
@@ -851,19 +858,25 @@ private List<Document> fromSchema() {
851858 collectPaths (root , null , paths );
852859
853860 List <Document > fields = new ArrayList <>();
854- if (!paths .isEmpty ()) {
861+ if (paths .isEmpty ()) {
862+ return fields ;
863+ }
855864
856- for (Entry <String , Document > entry : paths .entrySet ()) {
857- Document field = new Document ("path" , entry .getKey ());
858- field .append ("keyId" , entry .getValue ().getOrDefault ("keyId" , BsonNull .VALUE ));
859- if (entry .getValue ().containsKey ("bsonType" )) {
860- field .append ("bsonType" , entry .getValue ().get ("bsonType" ));
861- }
862- if (entry .getValue ().containsKey ("queries" )) {
863- field .put ("queries" , entry .getValue ().get ("queries" ));
864- }
865- fields .add (field );
865+ for (Entry <String , Document > entry : paths .entrySet ()) {
866+
867+ Document field = new Document ("path" , entry .getKey ());
868+
869+ field .append ("keyId" , entry .getValue ().getOrDefault ("keyId" , BsonNull .VALUE ));
870+
871+ if (entry .getValue ().containsKey ("bsonType" )) {
872+ field .append ("bsonType" , entry .getValue ().get ("bsonType" ));
873+ }
874+
875+ if (entry .getValue ().containsKey ("queries" )) {
876+ field .put ("queries" , entry .getValue ().get ("queries" ));
866877 }
878+
879+ fields .add (field );
867880 }
868881
869882 return fields ;
@@ -873,28 +886,29 @@ private List<Document> fromSchema() {
873886 private static void collectPaths (Document document , @ Nullable String currentPath , Map <String , Document > paths ) {
874887
875888 if (document .containsKey ("type" ) && document .get ("type" ).equals ("object" )) {
889+
876890 Object o = document .get ("properties" );
877- if (o == null ) {
891+
892+ if (!(o instanceof Document properties )) {
878893 return ;
879894 }
880895
881- if (o instanceof Document properties ) {
882- for (Entry <String , Object > entry : properties .entrySet ()) {
883- if (entry .getValue () instanceof Document nested ) {
884-
885- String path = currentPath == null ? entry .getKey () : (currentPath + "." + entry .getKey ());
886- if (nested .containsKey ("encrypt" )) {
887- Document target = new Document (nested .get ("encrypt" , Document .class ));
888- if (nested .containsKey ("queries" )) {
889- List <?> queries = nested .get ("queries" , List .class );
890- if (!queries .isEmpty () && queries .iterator ().next () instanceof Document qd ) {
891- target .putAll (qd );
892- }
896+ for (Entry <String , Object > entry : properties .entrySet ()) {
897+
898+ if (entry .getValue () instanceof Document nested ) {
899+
900+ String path = currentPath == null ? entry .getKey () : (currentPath + "." + entry .getKey ());
901+ if (nested .containsKey ("encrypt" )) {
902+ Document target = new Document (nested .get ("encrypt" , Document .class ));
903+ if (nested .containsKey ("queries" )) {
904+ List <?> queries = nested .get ("queries" , List .class );
905+ if (!queries .isEmpty () && queries .iterator ().next () instanceof Document qd ) {
906+ target .putAll (qd );
893907 }
894- paths .put (path , target );
895- } else {
896- collectPaths (nested , path , paths );
897908 }
909+ paths .put (path , target );
910+ } else {
911+ collectPaths (nested , path , paths );
898912 }
899913 }
900914 }
0 commit comments