Skip to content

Commit bb013a0

Browse files
authored
Ensure Operations uses the supplied document class when creating BsonDocumentWrapper (#1327)
JAVA-5349
1 parent 82f4a9f commit bb013a0

File tree

4 files changed

+392
-210
lines changed

4 files changed

+392
-210
lines changed

driver-core/src/main/com/mongodb/internal/operation/Operations.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,11 @@ private Codec<TDocument> getCodec() {
742742
}
743743

744744
private BsonDocument documentToBsonDocument(final TDocument document) {
745-
return BsonDocumentWrapper.asBsonDocument(document, codecRegistry);
745+
if (document instanceof BsonDocument) {
746+
return (BsonDocument) document;
747+
} else {
748+
return new BsonDocumentWrapper<>(document, getCodec());
749+
}
746750
}
747751

748752
@Nullable
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
17+
package com.mongodb.reactivestreams.client;
18+
19+
import com.mongodb.client.AbstractMongoCollectionTest;
20+
import com.mongodb.client.MongoDatabase;
21+
import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient;
22+
import org.junit.jupiter.api.AfterAll;
23+
24+
import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder;
25+
26+
public class MongoCollectionTest extends AbstractMongoCollectionTest {
27+
28+
private static com.mongodb.client.MongoClient mongoClient;
29+
30+
@Override
31+
protected MongoDatabase getDatabase(final String databaseName) {
32+
return createMongoClient().getDatabase(databaseName);
33+
}
34+
35+
private com.mongodb.client.MongoClient createMongoClient() {
36+
if (mongoClient == null) {
37+
mongoClient = new SyncMongoClient(MongoClients.create(getMongoClientSettingsBuilder().build()));
38+
}
39+
return mongoClient;
40+
}
41+
42+
43+
@AfterAll
44+
public static void closeClient() {
45+
if (mongoClient != null) {
46+
mongoClient.close();
47+
mongoClient = null;
48+
}
49+
}
50+
}
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
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+
17+
package com.mongodb.client;
18+
19+
import com.mongodb.DBRef;
20+
import com.mongodb.MongoClientSettings;
21+
import com.mongodb.ReadPreference;
22+
import com.mongodb.WriteConcern;
23+
import com.mongodb.client.result.InsertManyResult;
24+
import org.bson.BsonReader;
25+
import org.bson.BsonValue;
26+
import org.bson.BsonWriter;
27+
import org.bson.Document;
28+
import org.bson.RawBsonDocument;
29+
import org.bson.codecs.BsonValueCodecProvider;
30+
import org.bson.codecs.Codec;
31+
import org.bson.codecs.DecoderContext;
32+
import org.bson.codecs.DocumentCodecProvider;
33+
import org.bson.codecs.EncoderContext;
34+
import org.bson.codecs.ValueCodecProvider;
35+
import org.bson.codecs.configuration.CodecRegistry;
36+
import org.bson.codecs.pojo.PojoCodecProvider;
37+
import org.bson.codecs.pojo.entities.ShapeModelAbstract;
38+
import org.bson.codecs.pojo.entities.ShapeModelCircle;
39+
import org.bson.codecs.pojo.entities.conventions.BsonRepresentationModel;
40+
import org.bson.json.JsonObject;
41+
import org.bson.types.ObjectId;
42+
import org.junit.jupiter.api.AfterEach;
43+
import org.junit.jupiter.api.BeforeEach;
44+
import org.junit.jupiter.api.Test;
45+
46+
import java.util.ArrayList;
47+
import java.util.Collections;
48+
import java.util.HashMap;
49+
import java.util.List;
50+
import java.util.Map;
51+
52+
import static com.mongodb.ClusterFixture.isServerlessTest;
53+
import static com.mongodb.client.Fixture.getDefaultDatabaseName;
54+
import static java.util.Arrays.asList;
55+
import static org.bson.codecs.configuration.CodecRegistries.fromCodecs;
56+
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
57+
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
58+
import static org.hamcrest.CoreMatchers.is;
59+
import static org.hamcrest.MatcherAssert.assertThat;
60+
import static org.junit.jupiter.api.Assertions.assertEquals;
61+
import static org.junit.jupiter.api.Assertions.assertNotNull;
62+
import static org.junit.jupiter.api.Assertions.assertTrue;
63+
import static org.junit.jupiter.api.Assumptions.assumeFalse;
64+
65+
public abstract class AbstractMongoCollectionTest {
66+
67+
protected abstract MongoDatabase getDatabase(String databaseName);
68+
69+
MongoCollection<Document> getCollection() {
70+
return getDatabase(getDefaultDatabaseName()).getCollection("MongoCollectionTest");
71+
}
72+
73+
@BeforeEach
74+
public void setUp() {
75+
getCollection().drop();
76+
}
77+
78+
@AfterEach
79+
public void cleanUp() {
80+
getCollection().drop();
81+
}
82+
83+
@Test
84+
public void testFindAndUpdateWithGenerics() {
85+
CodecRegistry codecRegistry = fromProviders(asList(new ValueCodecProvider(), new DocumentCodecProvider(),
86+
new BsonValueCodecProvider(), new ConcreteCodecProvider()));
87+
MongoCollection<Concrete> collection = getCollection()
88+
.withDocumentClass(Concrete.class)
89+
.withCodecRegistry(codecRegistry)
90+
.withReadPreference(ReadPreference.primary())
91+
.withWriteConcern(WriteConcern.ACKNOWLEDGED);
92+
93+
Concrete doc = new Concrete(new ObjectId(), "str", 5, 10L, 4.0, 3290482390480L);
94+
collection.insertOne(doc);
95+
96+
Concrete newDoc = collection.findOneAndUpdate(new Document("i", 5),
97+
new Document("$set", new Document("i", 6)));
98+
99+
assertNotNull(newDoc);
100+
assertEquals(doc, newDoc);
101+
}
102+
103+
@Test
104+
public void testFindOneAndUpdateEmpty() {
105+
boolean exceptionFound = false;
106+
getCollection().insertOne(new Document().append("_id", "fakeId").append("one", 1).append("foo", "bar"));
107+
108+
try {
109+
getCollection().findOneAndUpdate(new Document(), new Document());
110+
} catch (IllegalArgumentException e) {
111+
assertEquals("Invalid BSON document for an update. The document may not be empty.", e.getMessage());
112+
exceptionFound = true;
113+
}
114+
assertTrue(exceptionFound);
115+
}
116+
117+
@Test
118+
public void shouldBeAbleToQueryTypedCollectionAndMapResultsIntoTypedLists() {
119+
// given
120+
CodecRegistry codecRegistry = fromProviders(asList(new ValueCodecProvider(), new DocumentCodecProvider(),
121+
new BsonValueCodecProvider(), new ConcreteCodecProvider()));
122+
MongoCollection<Concrete> collection = getCollection()
123+
.withDocumentClass(Concrete.class)
124+
.withCodecRegistry(codecRegistry)
125+
.withReadPreference(ReadPreference.primary())
126+
.withWriteConcern(WriteConcern.ACKNOWLEDGED);
127+
128+
Concrete firstItem = new Concrete("first", 1, 2L, 3.0, 5L);
129+
collection.insertOne(firstItem);
130+
131+
Concrete secondItem = new Concrete("second", 7, 11L, 13.0, 17L);
132+
collection.insertOne(secondItem);
133+
134+
// when
135+
List<String> listOfStringObjectIds = collection.find(new Document("i", 1))
136+
.map(Concrete::getId)
137+
.map(ObjectId::toString).into(new ArrayList<>());
138+
139+
// then
140+
assertThat(listOfStringObjectIds.size(), is(1));
141+
assertThat(listOfStringObjectIds.get(0), is(firstItem.getId().toString()));
142+
143+
// when
144+
List<ObjectId> listOfObjectIds = collection.find(new Document("i", 1))
145+
.map(Concrete::getId)
146+
.into(new ArrayList<>());
147+
148+
// then
149+
assertThat(listOfObjectIds.size(), is(1));
150+
assertThat(listOfObjectIds.get(0), is(firstItem.getId()));
151+
}
152+
153+
@SuppressWarnings("deprecation")
154+
@Test
155+
public void testMapReduceWithGenerics() {
156+
assumeFalse(isServerlessTest());
157+
158+
// given
159+
CodecRegistry codecRegistry = fromProviders(asList(new DocumentCodecProvider(), new NameCodecProvider()));
160+
getCollection().insertMany(asList(new Document("name", "Pete").append("job", "handyman"),
161+
new Document("name", "Sam").append("job", "Plumber"),
162+
new Document("name", "Pete").append("job", "'electrician'")));
163+
164+
String mapFunction = "function(){ emit( this.name , 1 ); }";
165+
String reduceFunction = "function(key, values){ return values.length; }";
166+
MongoCollection<Document> collection = getCollection()
167+
.withCodecRegistry(codecRegistry)
168+
.withReadPreference(ReadPreference.primary())
169+
.withWriteConcern(WriteConcern.ACKNOWLEDGED);
170+
171+
// when
172+
List<Name> result = collection.mapReduce(mapFunction, reduceFunction, Name.class).into(new ArrayList<>());
173+
174+
// then
175+
assertTrue(result.contains(new Name("Pete", 2)));
176+
assertTrue(result.contains(new Name("Sam", 1)));
177+
}
178+
179+
@Test
180+
public void testAggregationToACollection() {
181+
assumeFalse(isServerlessTest());
182+
183+
// given
184+
List<Document> documents = asList(new Document("_id", 1), new Document("_id", 2));
185+
getCollection().insertMany(documents);
186+
187+
188+
// when
189+
List<Document> result = getCollection().aggregate(Collections.singletonList(new Document("$out", "outCollection")))
190+
.into(new ArrayList<>());
191+
192+
// then
193+
assertEquals(documents, result);
194+
}
195+
196+
@Test
197+
public void bulkInsertRawBsonDocuments() {
198+
// given
199+
List<RawBsonDocument> docs = asList(RawBsonDocument.parse("{a: 1}"), RawBsonDocument.parse("{a: 2}"));
200+
201+
// when
202+
InsertManyResult result = getCollection().withDocumentClass(RawBsonDocument.class).insertMany(docs);
203+
204+
// then
205+
Map<Integer, BsonValue> expectedResult = new HashMap<>();
206+
expectedResult.put(0, null);
207+
expectedResult.put(1, null);
208+
assertEquals(expectedResult, result.getInsertedIds());
209+
}
210+
211+
// This is really a test that the default registry created in MongoClient and passed down to MongoCollection has been constructed
212+
// properly to handle DBRef encoding and decoding
213+
@Test
214+
public void testDBRefEncodingAndDecoding() {
215+
// given
216+
Document doc = new Document("_id", 1)
217+
.append("ref", new DBRef("foo", 5))
218+
.append("refWithDB", new DBRef("db", "foo", 5));
219+
220+
221+
// when
222+
getCollection().insertOne(doc);
223+
224+
// then
225+
assertEquals(doc, getCollection().find().first());
226+
}
227+
228+
@Test
229+
public void testJsonObjectEncodingAndDecoding() {
230+
// given
231+
MongoCollection<JsonObject> test = getCollection().withDocumentClass(JsonObject.class);
232+
JsonObject json = new JsonObject("{\"_id\": {\"$oid\": \"5f5a5442306e56d34136dbcf\"}, \"hello\": 1}");
233+
234+
// when
235+
test.insertOne(json);
236+
237+
// then
238+
assertEquals(json, test.find().first());
239+
}
240+
241+
@Test
242+
public void testObjectIdToStringConversion() {
243+
// given
244+
CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
245+
fromProviders(PojoCodecProvider.builder().automatic(true).build()));
246+
247+
MongoCollection<BsonRepresentationModel> test = getCollection()
248+
.withDocumentClass(BsonRepresentationModel.class)
249+
.withCodecRegistry(pojoCodecRegistry);
250+
test.drop();
251+
252+
// when
253+
test.insertOne(new BsonRepresentationModel(null, 1));
254+
255+
// then
256+
BsonRepresentationModel first = test.find().first();
257+
assertNotNull(first);
258+
assertNotNull(first.getId());
259+
}
260+
261+
@Test
262+
public void testOperationsUseDocumentClassCodec() {
263+
264+
Codec<ShapeModelAbstract> shapeModelCodec = new Codec<ShapeModelAbstract>() {
265+
private final CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
266+
fromProviders(PojoCodecProvider.builder().automatic(true).build()));
267+
268+
@Override
269+
public ShapeModelAbstract decode(final BsonReader reader, final DecoderContext decoderContext) {
270+
return pojoCodecRegistry.get(getEncoderClass()).decode(reader, decoderContext);
271+
}
272+
273+
@Override
274+
public void encode(final BsonWriter writer, final ShapeModelAbstract value, final EncoderContext encoderContext) {
275+
pojoCodecRegistry.get(getEncoderClass()).encode(writer, value, encoderContext);
276+
}
277+
278+
@Override
279+
public Class<ShapeModelAbstract> getEncoderClass() {
280+
return ShapeModelAbstract.class;
281+
}
282+
};
283+
Codec<ShapeModelCircle> circleCodec = new Codec<ShapeModelCircle>() {
284+
285+
@Override
286+
public void encode(final BsonWriter writer, final ShapeModelCircle value, final EncoderContext encoderContext) {
287+
throw new UnsupportedOperationException("If this method is called it means this codec was used directly, "
288+
+ "even though its not the MongoCollection document class.");
289+
}
290+
291+
@Override
292+
public Class<ShapeModelCircle> getEncoderClass() {
293+
return ShapeModelCircle.class;
294+
}
295+
296+
@Override
297+
public ShapeModelCircle decode(final BsonReader reader, final DecoderContext decoderContext) {
298+
throw new UnsupportedOperationException("If this method is called it means this codec was used directly, "
299+
+ "even though its not the MongoCollection document class.");
300+
}
301+
};
302+
303+
304+
// given
305+
CodecRegistry pojoCodecRegistry = fromRegistries(fromCodecs(shapeModelCodec, circleCodec),
306+
MongoClientSettings.getDefaultCodecRegistry());
307+
308+
MongoCollection<ShapeModelAbstract> test = getCollection()
309+
.withDocumentClass(ShapeModelAbstract.class)
310+
.withCodecRegistry(pojoCodecRegistry);
311+
test.drop();
312+
313+
// when
314+
ShapeModelCircle redCircle = new ShapeModelCircle("red", 1.1);
315+
test.insertOne(redCircle);
316+
317+
// then
318+
assertEquals(redCircle, test.find().first());
319+
}
320+
}

0 commit comments

Comments
 (0)