Skip to content

Commit a748926

Browse files
authored
Introduce BsonUtil.mutableDeepCopy (#1081)
JAVA-4874
1 parent d8b17c6 commit a748926

File tree

5 files changed

+203
-3
lines changed

5 files changed

+203
-3
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.bson.internal;
17+
18+
import org.bson.BsonArray;
19+
import org.bson.BsonBinary;
20+
import org.bson.BsonDocument;
21+
import org.bson.BsonJavaScriptWithScope;
22+
import org.bson.BsonValue;
23+
24+
/**
25+
* <p>This class is not part of the public API and may be removed or changed at any time</p>
26+
*/
27+
public final class BsonUtil {
28+
public static BsonDocument mutableDeepCopy(final BsonDocument original) {
29+
BsonDocument copy = new BsonDocument(original.size());
30+
original.forEach((key, value) -> copy.put(key, mutableDeepCopy(value)));
31+
return copy;
32+
}
33+
34+
private static BsonArray mutableDeepCopy(final BsonArray original) {
35+
BsonArray copy = new BsonArray(original.size());
36+
original.forEach(element -> copy.add(mutableDeepCopy(element)));
37+
return copy;
38+
}
39+
40+
private static BsonBinary mutableDeepCopy(final BsonBinary original) {
41+
return new BsonBinary(original.getType(), original.getData().clone());
42+
}
43+
44+
private static BsonJavaScriptWithScope mutableDeepCopy(final BsonJavaScriptWithScope original) {
45+
return new BsonJavaScriptWithScope(original.getCode(), mutableDeepCopy(original.getScope()));
46+
}
47+
48+
private static BsonValue mutableDeepCopy(final BsonValue original) {
49+
switch (original.getBsonType()) {
50+
case DOCUMENT:
51+
return mutableDeepCopy(original.asDocument());
52+
case ARRAY:
53+
return mutableDeepCopy(original.asArray());
54+
case BINARY:
55+
return mutableDeepCopy(original.asBinary());
56+
case JAVASCRIPT_WITH_SCOPE:
57+
return mutableDeepCopy(original.asJavaScriptWithScope());
58+
default:
59+
return original;
60+
}
61+
}
62+
63+
private BsonUtil() {
64+
}
65+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.bson.internal;
17+
18+
import org.bson.BsonArray;
19+
import org.bson.BsonBinary;
20+
import org.bson.BsonDocument;
21+
import org.bson.BsonDocumentWrapper;
22+
import org.bson.BsonJavaScriptWithScope;
23+
import org.bson.BsonValue;
24+
import org.bson.RawBsonArray;
25+
import org.bson.RawBsonDocument;
26+
import org.bson.conversions.Bson;
27+
import org.junit.jupiter.api.Test;
28+
29+
import java.nio.charset.StandardCharsets;
30+
import java.util.AbstractMap.SimpleImmutableEntry;
31+
import java.util.Map.Entry;
32+
33+
import static java.util.Arrays.asList;
34+
import static java.util.Collections.singletonList;
35+
import static org.junit.jupiter.api.Assertions.assertEquals;
36+
import static org.junit.jupiter.api.Assertions.assertNotSame;
37+
38+
final class BsonUtilTest {
39+
@Test
40+
public void mutableDeepCopy() {
41+
Entry<String, BsonBinary> originalBsonBinaryEntry = new SimpleImmutableEntry<>(
42+
"bsonBinary",
43+
new BsonBinary("bsonBinary".getBytes(StandardCharsets.UTF_8))
44+
);
45+
Entry<String, BsonJavaScriptWithScope> originalBsonJavaScriptWithScopeEntry = new SimpleImmutableEntry<>(
46+
"bsonJavaScriptWithScopeEntry",
47+
new BsonJavaScriptWithScope("\"use strict\";", new BsonDocument())
48+
);
49+
Entry<String, RawBsonDocument> originalRawBsonDocumentEntry = new SimpleImmutableEntry<>(
50+
"rawBsonDocument",
51+
RawBsonDocument.parse("{rawBsonDocument: 'rawBsonDocument_value'}")
52+
);
53+
Entry<String, BsonDocumentWrapper<RawBsonDocument>> originalBsonDocumentWrapperEntry = new SimpleImmutableEntry<>(
54+
"bsonDocumentWrapper",
55+
new BsonDocumentWrapper<>(originalRawBsonDocumentEntry.getValue(), Bson.DEFAULT_CODEC_REGISTRY.get(RawBsonDocument.class))
56+
);
57+
Entry<String, BsonDocument> originalBsonDocumentEntry = new SimpleImmutableEntry<>(
58+
"bsonDocument",
59+
new BsonDocument()
60+
.append(originalBsonBinaryEntry.getKey(), originalBsonBinaryEntry.getValue())
61+
.append(originalBsonJavaScriptWithScopeEntry.getKey(), originalBsonJavaScriptWithScopeEntry.getValue())
62+
.append(originalRawBsonDocumentEntry.getKey(), originalRawBsonDocumentEntry.getValue())
63+
.append(originalBsonDocumentWrapperEntry.getKey(), originalBsonDocumentWrapperEntry.getValue())
64+
);
65+
Entry<String, BsonArray> originalBsonArrayEntry = new SimpleImmutableEntry<>(
66+
"bsonArray",
67+
new BsonArray(singletonList(new BsonArray()))
68+
);
69+
Entry<String, RawBsonArray> originalRawBsonArrayEntry = new SimpleImmutableEntry<>(
70+
"rawBsonArray",
71+
rawBsonArray(
72+
originalBsonBinaryEntry.getValue(),
73+
originalBsonJavaScriptWithScopeEntry.getValue(),
74+
originalRawBsonDocumentEntry.getValue(),
75+
originalBsonDocumentWrapperEntry.getValue(),
76+
originalBsonDocumentEntry.getValue(),
77+
originalBsonArrayEntry.getValue())
78+
);
79+
BsonDocument original = new BsonDocument()
80+
.append(originalBsonBinaryEntry.getKey(), originalBsonBinaryEntry.getValue())
81+
.append(originalBsonJavaScriptWithScopeEntry.getKey(), originalBsonJavaScriptWithScopeEntry.getValue())
82+
.append(originalRawBsonDocumentEntry.getKey(), originalRawBsonDocumentEntry.getValue())
83+
.append(originalBsonDocumentWrapperEntry.getKey(), originalBsonDocumentWrapperEntry.getValue())
84+
.append(originalBsonDocumentEntry.getKey(), originalBsonDocumentEntry.getValue())
85+
.append(originalBsonArrayEntry.getKey(), originalBsonArrayEntry.getValue())
86+
.append(originalRawBsonArrayEntry.getKey(), originalRawBsonArrayEntry.getValue());
87+
BsonDocument copy = BsonUtil.mutableDeepCopy(original);
88+
assertEqualNotSameAndMutable(original, copy);
89+
original.forEach((key, value) -> assertEqualNotSameAndMutable(value, copy.get(key)));
90+
// check nested document
91+
String nestedDocumentKey = originalBsonDocumentEntry.getKey();
92+
BsonDocument originalNestedDocument = original.getDocument(nestedDocumentKey);
93+
BsonDocument copyNestedDocument = copy.getDocument(nestedDocumentKey);
94+
assertEqualNotSameAndMutable(originalNestedDocument, copyNestedDocument);
95+
originalNestedDocument.forEach((key, value) -> assertEqualNotSameAndMutable(value, copyNestedDocument.get(key)));
96+
// check nested array
97+
String nestedArrayKey = originalRawBsonArrayEntry.getKey();
98+
BsonArray originalNestedArray = original.getArray(nestedArrayKey);
99+
BsonArray copyNestedArray = copy.getArray(nestedArrayKey);
100+
assertEqualNotSameAndMutable(originalNestedArray, copyNestedArray);
101+
for (int i = 0; i < originalNestedArray.size(); i++) {
102+
assertEqualNotSameAndMutable(originalNestedArray.get(i), copyNestedArray.get(i));
103+
}
104+
}
105+
106+
private static RawBsonArray rawBsonArray(final BsonValue... elements) {
107+
return (RawBsonArray) new RawBsonDocument(
108+
new BsonDocument("a", new BsonArray(asList(elements))), Bson.DEFAULT_CODEC_REGISTRY.get(BsonDocument.class))
109+
.get("a");
110+
}
111+
112+
private static void assertEqualNotSameAndMutable(final Object expected, final Object actual) {
113+
assertEquals(expected, actual);
114+
assertNotSame(expected, actual);
115+
Class<?> actualClass = actual.getClass();
116+
if (expected instanceof BsonDocument) {
117+
assertEquals(BsonDocument.class, actualClass);
118+
} else if (expected instanceof BsonArray) {
119+
assertEquals(BsonArray.class, actualClass);
120+
} else if (expected instanceof BsonBinary) {
121+
assertEquals(BsonBinary.class, actualClass);
122+
} else if (expected instanceof BsonJavaScriptWithScope) {
123+
assertEquals(BsonJavaScriptWithScope.class, actualClass);
124+
} else {
125+
org.bson.assertions.Assertions.fail("Unexpected " + expected.getClass().toString());
126+
}
127+
}
128+
129+
private BsonUtilTest() {
130+
}
131+
}

driver-core/src/main/com/mongodb/internal/client/model/AbstractConstructibleBson.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.util.Optional;
2828
import java.util.function.Consumer;
2929

30+
import static org.bson.internal.BsonUtil.mutableDeepCopy;
31+
3032
/**
3133
* A {@link Bson} that allows constructing new instances via {@link #newAppended(String, Object)} instead of mutating {@code this}.
3234
* See {@link #AbstractConstructibleBson(Bson, Document)} for the note on mutability.
@@ -141,7 +143,7 @@ public String toString() {
141143
}
142144

143145
static BsonDocument newMerged(final BsonDocument base, final BsonDocument appended) {
144-
BsonDocument result = base.clone();
146+
BsonDocument result = mutableDeepCopy(base);
145147
result.putAll(appended);
146148
return result;
147149
}

driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/vault/ClientEncryptionImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import static java.lang.String.format;
6464
import static java.util.Arrays.asList;
6565
import static java.util.Collections.singletonList;
66+
import static org.bson.internal.BsonUtil.mutableDeepCopy;
6667

6768
/**
6869
* <p>This class is not part of the public API and may be removed or changed at any time</p>
@@ -223,7 +224,7 @@ public Publisher<BsonDocument> createEncryptedCollection(final MongoDatabase dat
223224
return Mono.defer(() -> {
224225
// `Mono.defer` results in `maybeUpdatedEncryptedFields` and `dataKeyMightBeCreated` (mutable state)
225226
// being created once per `Subscriber`, which allows the produced `Mono` to support multiple `Subscribers`.
226-
BsonDocument maybeUpdatedEncryptedFields = encryptedFields.clone();
227+
BsonDocument maybeUpdatedEncryptedFields = mutableDeepCopy(encryptedFields);
227228
AtomicBoolean dataKeyMightBeCreated = new AtomicBoolean();
228229
Iterable<Mono<BsonDocument>> publishersOfUpdatedFields = () -> maybeUpdatedEncryptedFields.get("fields").asArray()
229230
.stream()

driver-sync/src/main/com/mongodb/client/internal/ClientEncryptionImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import static java.lang.String.format;
5959
import static java.util.Arrays.asList;
6060
import static java.util.Collections.singletonList;
61+
import static org.bson.internal.BsonUtil.mutableDeepCopy;
6162

6263
/**
6364
* <p>This class is not part of the public API and may be removed or changed at any time</p>
@@ -207,7 +208,7 @@ public BsonDocument createEncryptedCollection(final MongoDatabase database, fina
207208
dataKeyOptions.masterKey(masterKey);
208209
}
209210
String keyIdBsonKey = "keyId";
210-
BsonDocument maybeUpdatedEncryptedFields = encryptedFields.clone();
211+
BsonDocument maybeUpdatedEncryptedFields = mutableDeepCopy(encryptedFields);
211212
// only the mutability of `dataKeyMightBeCreated` is important, it does not need to be thread-safe
212213
AtomicBoolean dataKeyMightBeCreated = new AtomicBoolean();
213214
try {

0 commit comments

Comments
 (0)