16
16
17
17
package com .mongodb .internal .connection ;
18
18
19
+ import com .mongodb .internal .connection .DualMessageSequences .EncodeDocumentsResult ;
20
+ import com .mongodb .internal .connection .DualMessageSequences .WritersProviderAndLimitsChecker ;
21
+ import com .mongodb .lang .Nullable ;
22
+ import org .bson .BsonBinaryWriter ;
23
+ import org .bson .BsonBinaryWriterSettings ;
24
+ import org .bson .BsonContextType ;
19
25
import org .bson .BsonDocument ;
20
26
import org .bson .BsonElement ;
21
27
import org .bson .BsonMaximumSizeExceededException ;
22
28
import org .bson .BsonValue ;
23
29
import org .bson .BsonWriter ;
30
+ import org .bson .BsonWriterSettings ;
31
+ import org .bson .FieldNameValidator ;
24
32
import org .bson .codecs .BsonValueCodecProvider ;
25
- import org .bson .codecs .Codec ;
33
+ import org .bson .codecs .Encoder ;
26
34
import org .bson .codecs .EncoderContext ;
27
35
import org .bson .codecs .configuration .CodecRegistry ;
28
36
import org .bson .io .BsonOutput ;
29
37
30
38
import java .util .List ;
31
39
40
+ import static com .mongodb .assertions .Assertions .assertTrue ;
41
+ import static com .mongodb .internal .connection .DualMessageSequences .WritersProviderAndLimitsChecker .WriteResult .FAIL_LIMIT_EXCEEDED ;
42
+ import static com .mongodb .internal .connection .DualMessageSequences .WritersProviderAndLimitsChecker .WriteResult .OK_LIMIT_NOT_REACHED ;
43
+ import static com .mongodb .internal .connection .DualMessageSequences .WritersProviderAndLimitsChecker .WriteResult .OK_LIMIT_REACHED ;
44
+ import static com .mongodb .internal .connection .MessageSettings .DOCUMENT_HEADROOM_SIZE ;
32
45
import static java .lang .String .format ;
33
46
import static org .bson .codecs .configuration .CodecRegistries .fromProviders ;
34
47
35
- final class BsonWriterHelper {
36
- private static final int DOCUMENT_HEADROOM = 1024 * 16 ;
48
+ /**
49
+ * This class is not part of the public API and may be removed or changed at any time.
50
+ */
51
+ public final class BsonWriterHelper {
37
52
private static final CodecRegistry REGISTRY = fromProviders (new BsonValueCodecProvider ());
38
53
private static final EncoderContext ENCODER_CONTEXT = EncoderContext .builder ().build ();
39
54
40
- static void writeElements (final BsonWriter writer , final List <BsonElement > bsonElements ) {
41
- for (BsonElement bsonElement : bsonElements ) {
42
- writer .writeName (bsonElement .getName ());
43
- getCodec (bsonElement .getValue ()).encode (writer , bsonElement .getValue (), ENCODER_CONTEXT );
55
+ static void appendElementsToDocument (
56
+ final BsonOutput bsonOutputWithDocument ,
57
+ final int documentStartPosition ,
58
+ @ Nullable final List <BsonElement > bsonElements ) {
59
+ if ((bsonElements == null ) || bsonElements .isEmpty ()) {
60
+ return ;
61
+ }
62
+ try (AppendingBsonWriter writer = new AppendingBsonWriter (bsonOutputWithDocument , documentStartPosition )) {
63
+ for (BsonElement element : bsonElements ) {
64
+ String name = element .getName ();
65
+ BsonValue value = element .getValue ();
66
+ writer .writeName (name );
67
+ encodeUsingRegistry (writer , value );
68
+ }
44
69
}
45
70
}
46
71
@@ -65,16 +90,86 @@ static void writePayload(final BsonWriter writer, final BsonOutput bsonOutput, f
65
90
}
66
91
67
92
if (payload .getPosition () == 0 ) {
68
- throw new BsonMaximumSizeExceededException (format ("Payload document size is larger than maximum of %d." ,
69
- payloadSettings .getMaxDocumentSize ()));
93
+ throw createBsonMaximumSizeExceededException (payloadSettings .getMaxDocumentSize ());
70
94
}
71
95
}
72
96
97
+ /**
98
+ * @return See {@link DualMessageSequences#encodeDocuments(WritersProviderAndLimitsChecker)}.
99
+ */
100
+ static EncodeDocumentsResult writeDocumentsOfDualMessageSequences (
101
+ final DualMessageSequences dualMessageSequences ,
102
+ final int commandDocumentSizeInBytes ,
103
+ final BsonOutput firstOutput ,
104
+ final BsonOutput secondOutput ,
105
+ final MessageSettings messageSettings ) {
106
+ BsonBinaryWriter firstWriter = createBsonBinaryWriter (firstOutput , dualMessageSequences .getFirstFieldNameValidator (), null );
107
+ BsonBinaryWriter secondWriter = createBsonBinaryWriter (secondOutput , dualMessageSequences .getSecondFieldNameValidator (), null );
108
+ // the size of operation-agnostic command fields (a.k.a. extra elements) is counted towards `messageOverheadInBytes`
109
+ int messageOverheadInBytes = 1000 ;
110
+ int maxSizeInBytes = messageSettings .getMaxMessageSize () - (messageOverheadInBytes + commandDocumentSizeInBytes );
111
+ int firstStart = firstOutput .getPosition ();
112
+ int secondStart = secondOutput .getPosition ();
113
+ int maxBatchCount = messageSettings .getMaxBatchCount ();
114
+ return dualMessageSequences .encodeDocuments (writeAction -> {
115
+ int firstBeforeWritePosition = firstOutput .getPosition ();
116
+ int secondBeforeWritePosition = secondOutput .getPosition ();
117
+ int batchCountAfterWrite = writeAction .doAndGetBatchCount (firstWriter , secondWriter );
118
+ assertTrue (batchCountAfterWrite <= maxBatchCount );
119
+ int writtenSizeInBytes =
120
+ firstOutput .getPosition () - firstStart
121
+ + secondOutput .getPosition () - secondStart ;
122
+ if (writtenSizeInBytes < maxSizeInBytes && batchCountAfterWrite < maxBatchCount ) {
123
+ return OK_LIMIT_NOT_REACHED ;
124
+ } else if (writtenSizeInBytes > maxSizeInBytes ) {
125
+ firstOutput .truncateToPosition (firstBeforeWritePosition );
126
+ secondOutput .truncateToPosition (secondBeforeWritePosition );
127
+ if (batchCountAfterWrite == 1 ) {
128
+ // we have failed to write a single document
129
+ throw createBsonMaximumSizeExceededException (messageSettings .getMaxDocumentSize ());
130
+ }
131
+ return FAIL_LIMIT_EXCEEDED ;
132
+ } else {
133
+ return OK_LIMIT_REACHED ;
134
+ }
135
+ });
136
+ }
137
+
138
+ /**
139
+ * @param messageSettings Non-{@code null} iff the document size limit must be validated.
140
+ */
141
+ static BsonBinaryWriter createBsonBinaryWriter (
142
+ final BsonOutput out ,
143
+ final FieldNameValidator validator ,
144
+ @ Nullable final MessageSettings messageSettings ) {
145
+ return new BsonBinaryWriter (
146
+ new BsonWriterSettings (),
147
+ messageSettings == null
148
+ ? new BsonBinaryWriterSettings ()
149
+ : new BsonBinaryWriterSettings (messageSettings .getMaxDocumentSize () + DOCUMENT_HEADROOM_SIZE ),
150
+ out ,
151
+ validator );
152
+ }
153
+
154
+ /**
155
+ * Backpatches the document/message/sequence length into the beginning of the document/message/sequence.
156
+ *
157
+ * @param startPosition The start position of the document/message/sequence in {@code bsonOutput}.
158
+ */
159
+ static void backpatchLength (final int startPosition , final BsonOutput bsonOutput ) {
160
+ int messageLength = bsonOutput .getPosition () - startPosition ;
161
+ bsonOutput .writeInt32 (startPosition , messageLength );
162
+ }
163
+
164
+ private static BsonMaximumSizeExceededException createBsonMaximumSizeExceededException (final int maxSize ) {
165
+ return new BsonMaximumSizeExceededException (format ("Payload document size is larger than maximum of %d." , maxSize ));
166
+ }
167
+
73
168
private static boolean writeDocument (final BsonWriter writer , final BsonOutput bsonOutput , final MessageSettings settings ,
74
169
final BsonDocument document , final int messageStartPosition , final int batchItemCount ,
75
170
final int maxSplittableDocumentSize ) {
76
171
int currentPosition = bsonOutput .getPosition ();
77
- getCodec ( document ). encode ( writer , document , ENCODER_CONTEXT );
172
+ encodeUsingRegistry ( writer , document );
78
173
int messageSize = bsonOutput .getPosition () - messageStartPosition ;
79
174
int documentSize = bsonOutput .getPosition () - currentPosition ;
80
175
if (exceedsLimits (settings , messageSize , documentSize , batchItemCount )
@@ -85,24 +180,25 @@ private static boolean writeDocument(final BsonWriter writer, final BsonOutput b
85
180
return true ;
86
181
}
87
182
88
- @ SuppressWarnings ({"unchecked" })
89
- private static Codec <BsonValue > getCodec (final BsonValue bsonValue ) {
90
- return (Codec <BsonValue >) REGISTRY .get (bsonValue .getClass ());
183
+ static void encodeUsingRegistry (final BsonWriter writer , final BsonValue value ) {
184
+ @ SuppressWarnings ("unchecked" )
185
+ Encoder <BsonValue > encoder = (Encoder <BsonValue >) REGISTRY .get (value .getClass ());
186
+ encoder .encode (writer , value , ENCODER_CONTEXT );
91
187
}
92
188
93
189
private static MessageSettings getPayloadMessageSettings (final SplittablePayload .Type type , final MessageSettings settings ) {
94
190
MessageSettings payloadMessageSettings = settings ;
95
191
if (type != SplittablePayload .Type .INSERT ) {
96
192
payloadMessageSettings = createMessageSettingsBuilder (settings )
97
- .maxDocumentSize (settings .getMaxDocumentSize () + DOCUMENT_HEADROOM )
193
+ .maxDocumentSize (settings .getMaxDocumentSize () + DOCUMENT_HEADROOM_SIZE )
98
194
.build ();
99
195
}
100
196
return payloadMessageSettings ;
101
197
}
102
198
103
199
private static MessageSettings getDocumentMessageSettings (final MessageSettings settings ) {
104
200
return createMessageSettingsBuilder (settings )
105
- .maxMessageSize (settings .getMaxDocumentSize () + DOCUMENT_HEADROOM )
201
+ .maxMessageSize (settings .getMaxDocumentSize () + DOCUMENT_HEADROOM_SIZE )
106
202
.build ();
107
203
}
108
204
@@ -126,8 +222,50 @@ private static boolean exceedsLimits(final MessageSettings settings, final int m
126
222
return false ;
127
223
}
128
224
225
+ /**
226
+ * A {@link BsonWriter} that allows appending key/value pairs to a document that has been fully written to a {@link BsonOutput}.
227
+ */
228
+ private static final class AppendingBsonWriter extends LevelCountingBsonWriter implements AutoCloseable {
229
+ private static final int INITIAL_LEVEL = DEFAULT_INITIAL_LEVEL + 1 ;
129
230
130
- private BsonWriterHelper () {
231
+ /**
232
+ * @param bsonOutputWithDocument A {@link BsonOutput} {@linkplain BsonOutput#getPosition() positioned}
233
+ * immediately after the end of the document.
234
+ * @param documentStartPosition The {@linkplain BsonOutput#getPosition() position} of the start of the document
235
+ * in {@code bsonOutputWithDocument}.
236
+ */
237
+ AppendingBsonWriter (final BsonOutput bsonOutputWithDocument , final int documentStartPosition ) {
238
+ super (
239
+ new InternalAppendingBsonBinaryWriter (bsonOutputWithDocument , documentStartPosition ),
240
+ INITIAL_LEVEL );
241
+ }
242
+
243
+ @ Override
244
+ public void writeEndDocument () {
245
+ assertTrue (getCurrentLevel () > INITIAL_LEVEL );
246
+ super .writeEndDocument ();
247
+ }
248
+
249
+ @ Override
250
+ public void close () {
251
+ try (InternalAppendingBsonBinaryWriter writer = (InternalAppendingBsonBinaryWriter ) getBsonWriter ()) {
252
+ writer .writeEndDocument ();
253
+ }
254
+ }
255
+
256
+ private static final class InternalAppendingBsonBinaryWriter extends BsonBinaryWriter {
257
+ InternalAppendingBsonBinaryWriter (final BsonOutput bsonOutputWithDocument , final int documentStartPosition ) {
258
+ super (bsonOutputWithDocument );
259
+ int documentEndPosition = bsonOutputWithDocument .getPosition ();
260
+ int bsonDocumentEndingSize = 1 ;
261
+ int appendFromPosition = documentEndPosition - bsonDocumentEndingSize ;
262
+ bsonOutputWithDocument .truncateToPosition (appendFromPosition );
263
+ setState (State .NAME );
264
+ setContext (new Context (null , BsonContextType .DOCUMENT , documentStartPosition ));
265
+ }
266
+ }
131
267
}
132
268
269
+ private BsonWriterHelper () {
270
+ }
133
271
}
0 commit comments