com.mongodb.internal.connection.CommandMessage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongodb-driver-core Show documentation
Show all versions of mongodb-driver-core Show documentation
The Java operations layer for the MongoDB Java Driver. Third parties can ' +
'wrap this layer to provide custom higher-level APIs
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.internal.connection;
import com.mongodb.MongoClientException;
import com.mongodb.MongoNamespace;
import com.mongodb.ReadPreference;
import com.mongodb.ServerApi;
import com.mongodb.connection.ClusterConnectionMode;
import com.mongodb.internal.session.SessionContext;
import com.mongodb.lang.Nullable;
import org.bson.BsonArray;
import org.bson.BsonBinaryWriter;
import org.bson.BsonBoolean;
import org.bson.BsonDocument;
import org.bson.BsonElement;
import org.bson.BsonInt64;
import org.bson.BsonString;
import org.bson.FieldNameValidator;
import org.bson.io.BsonOutput;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static com.mongodb.ReadPreference.primary;
import static com.mongodb.ReadPreference.primaryPreferred;
import static com.mongodb.assertions.Assertions.assertFalse;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.connection.ClusterConnectionMode.LOAD_BALANCED;
import static com.mongodb.connection.ClusterConnectionMode.SINGLE;
import static com.mongodb.connection.ServerType.SHARD_ROUTER;
import static com.mongodb.connection.ServerType.STANDALONE;
import static com.mongodb.internal.connection.BsonWriterHelper.writePayload;
import static com.mongodb.internal.connection.ReadConcernHelper.getReadConcernDocument;
import static com.mongodb.internal.operation.ServerVersionHelper.FOUR_DOT_TWO_WIRE_VERSION;
import static com.mongodb.internal.operation.ServerVersionHelper.FOUR_DOT_ZERO_WIRE_VERSION;
import static com.mongodb.internal.operation.ServerVersionHelper.THREE_DOT_SIX_WIRE_VERSION;
/**
* A command message that uses OP_MSG or OP_QUERY to send the command.
*
* This class is not part of the public API and may be removed or changed at any time
*/
public final class CommandMessage extends RequestMessage {
private final MongoNamespace namespace;
private final BsonDocument command;
private final FieldNameValidator commandFieldNameValidator;
private final ReadPreference readPreference;
private final boolean exhaustAllowed;
private final SplittablePayload payload;
private final FieldNameValidator payloadFieldNameValidator;
private final boolean responseExpected;
private final ClusterConnectionMode clusterConnectionMode;
private final ServerApi serverApi;
CommandMessage(final MongoNamespace namespace, final BsonDocument command, final FieldNameValidator commandFieldNameValidator,
final ReadPreference readPreference, final MessageSettings settings, final ClusterConnectionMode clusterConnectionMode,
@Nullable final ServerApi serverApi) {
this(namespace, command, commandFieldNameValidator, readPreference, settings, true, null, null,
clusterConnectionMode, serverApi);
}
CommandMessage(final MongoNamespace namespace, final BsonDocument command, final FieldNameValidator commandFieldNameValidator,
final ReadPreference readPreference, final MessageSettings settings, final boolean exhaustAllowed,
final ClusterConnectionMode clusterConnectionMode, @Nullable final ServerApi serverApi) {
this(namespace, command, commandFieldNameValidator, readPreference, settings, true, exhaustAllowed, null, null,
clusterConnectionMode, serverApi);
}
CommandMessage(final MongoNamespace namespace, final BsonDocument command, final FieldNameValidator commandFieldNameValidator,
final ReadPreference readPreference, final MessageSettings settings, final boolean responseExpected,
@Nullable final SplittablePayload payload, @Nullable final FieldNameValidator payloadFieldNameValidator,
final ClusterConnectionMode clusterConnectionMode, @Nullable final ServerApi serverApi) {
this(namespace, command, commandFieldNameValidator, readPreference, settings, responseExpected, false, payload,
payloadFieldNameValidator, clusterConnectionMode, serverApi);
}
CommandMessage(final MongoNamespace namespace, final BsonDocument command, final FieldNameValidator commandFieldNameValidator,
final ReadPreference readPreference, final MessageSettings settings,
final boolean responseExpected, final boolean exhaustAllowed,
@Nullable final SplittablePayload payload, @Nullable final FieldNameValidator payloadFieldNameValidator,
final ClusterConnectionMode clusterConnectionMode, @Nullable final ServerApi serverApi) {
super(namespace.getFullName(), getOpCode(settings, clusterConnectionMode, serverApi), settings);
this.namespace = namespace;
this.command = command;
this.commandFieldNameValidator = commandFieldNameValidator;
this.readPreference = readPreference;
this.responseExpected = responseExpected;
this.exhaustAllowed = exhaustAllowed;
this.payload = payload;
this.payloadFieldNameValidator = payloadFieldNameValidator;
this.clusterConnectionMode = notNull("clusterConnectionMode", clusterConnectionMode);
this.serverApi = serverApi;
}
BsonDocument getCommandDocument(final ByteBufferBsonOutput bsonOutput) {
ByteBufBsonDocument byteBufBsonDocument = ByteBufBsonDocument.createOne(bsonOutput,
getEncodingMetadata().getFirstDocumentPosition());
BsonDocument commandBsonDocument;
if (containsPayload()) {
commandBsonDocument = byteBufBsonDocument.toBaseBsonDocument();
int payloadStartPosition = getEncodingMetadata().getFirstDocumentPosition()
+ byteBufBsonDocument.getSizeInBytes()
+ 1 // payload type
+ 4 // payload size
+ payload.getPayloadName().getBytes(StandardCharsets.UTF_8).length + 1; // null-terminated UTF-8 payload name
commandBsonDocument.append(payload.getPayloadName(),
new BsonArray(ByteBufBsonDocument.createList(bsonOutput, payloadStartPosition)));
} else {
commandBsonDocument = byteBufBsonDocument;
}
return commandBsonDocument;
}
boolean containsPayload() {
return payload != null;
}
boolean isResponseExpected() {
return !useOpMsg() || requireOpMsgResponse();
}
MongoNamespace getNamespace() {
return namespace;
}
@Override
protected EncodingMetadata encodeMessageBodyWithMetadata(final BsonOutput bsonOutput, final SessionContext sessionContext) {
int messageStartPosition = bsonOutput.getPosition() - MESSAGE_PROLOGUE_LENGTH;
int commandStartPosition;
if (useOpMsg()) {
int flagPosition = bsonOutput.getPosition();
bsonOutput.writeInt32(0); // flag bits
bsonOutput.writeByte(0); // payload type
commandStartPosition = bsonOutput.getPosition();
addDocument(command, bsonOutput, commandFieldNameValidator, getExtraElements(sessionContext));
if (payload != null) {
bsonOutput.writeByte(1); // payload type
int payloadBsonOutputStartPosition = bsonOutput.getPosition();
bsonOutput.writeInt32(0); // size
bsonOutput.writeCString(payload.getPayloadName());
writePayload(new BsonBinaryWriter(bsonOutput, payloadFieldNameValidator), bsonOutput, getSettings(),
messageStartPosition, payload, getSettings().getMaxDocumentSize());
int payloadBsonOutputLength = bsonOutput.getPosition() - payloadBsonOutputStartPosition;
bsonOutput.writeInt32(payloadBsonOutputStartPosition, payloadBsonOutputLength);
}
// Write the flag bits
bsonOutput.writeInt32(flagPosition, getOpMsgFlagBits());
} else {
bsonOutput.writeInt32(0);
bsonOutput.writeCString(namespace.getFullName());
bsonOutput.writeInt32(0);
bsonOutput.writeInt32(-1);
commandStartPosition = bsonOutput.getPosition();
List elements = null;
if (serverApi != null) {
elements = new ArrayList<>(3);
addServerApiElements(elements);
}
addDocument(command, bsonOutput, commandFieldNameValidator, elements);
}
return new EncodingMetadata(commandStartPosition);
}
private int getOpMsgFlagBits() {
int flagBits = 0;
if (!requireOpMsgResponse()) {
flagBits = 1 << 1;
}
if (exhaustAllowed) {
flagBits |= 1 << 16;
}
return flagBits;
}
private boolean requireOpMsgResponse() {
if (responseExpected) {
return true;
} else {
return payload != null && payload.hasAnotherSplit();
}
}
private boolean isDirectConnectionToReplicaSetMember() {
return clusterConnectionMode == SINGLE
&& getSettings().getServerType() != SHARD_ROUTER
&& getSettings().getServerType() != STANDALONE;
}
private boolean useOpMsg() {
return getOpCode().equals(OpCode.OP_MSG);
}
private List getExtraElements(final SessionContext sessionContext) {
List extraElements = new ArrayList<>();
extraElements.add(new BsonElement("$db", new BsonString(new MongoNamespace(getCollectionName()).getDatabaseName())));
if (sessionContext.getClusterTime() != null) {
extraElements.add(new BsonElement("$clusterTime", sessionContext.getClusterTime()));
}
if (sessionContext.hasSession() && responseExpected) {
extraElements.add(new BsonElement("lsid", sessionContext.getSessionId()));
}
boolean firstMessageInTransaction = sessionContext.notifyMessageSent();
assertFalse(sessionContext.hasActiveTransaction() && sessionContext.isSnapshot());
if (sessionContext.hasActiveTransaction()) {
checkServerVersionForTransactionSupport();
extraElements.add(new BsonElement("txnNumber", new BsonInt64(sessionContext.getTransactionNumber())));
if (firstMessageInTransaction) {
extraElements.add(new BsonElement("startTransaction", BsonBoolean.TRUE));
addReadConcernDocument(extraElements, sessionContext);
}
extraElements.add(new BsonElement("autocommit", BsonBoolean.FALSE));
} else if (sessionContext.isSnapshot()) {
addReadConcernDocument(extraElements, sessionContext);
}
if (serverApi != null) {
addServerApiElements(extraElements);
}
if (readPreference != null) {
if (!readPreference.equals(primary())) {
extraElements.add(new BsonElement("$readPreference", readPreference.toDocument()));
} else if (isDirectConnectionToReplicaSetMember()) {
extraElements.add(new BsonElement("$readPreference", primaryPreferred().toDocument()));
}
}
return extraElements;
}
private void addServerApiElements(final List extraElements) {
extraElements.add(new BsonElement("apiVersion", new BsonString(serverApi.getVersion().getValue())));
if (serverApi.getStrict().isPresent()) {
extraElements.add(new BsonElement("apiStrict", BsonBoolean.valueOf(serverApi.getStrict().get())));
}
if (serverApi.getDeprecationErrors().isPresent()) {
extraElements.add(new BsonElement("apiDeprecationErrors", BsonBoolean.valueOf(serverApi.getDeprecationErrors().get())));
}
}
private void checkServerVersionForTransactionSupport() {
int wireVersion = getSettings().getMaxWireVersion();
if (wireVersion < FOUR_DOT_ZERO_WIRE_VERSION
|| (wireVersion < FOUR_DOT_TWO_WIRE_VERSION && getSettings().getServerType() == SHARD_ROUTER)) {
throw new MongoClientException("Transactions are not supported by the MongoDB cluster to which this client is connected.");
}
}
private void addReadConcernDocument(final List extraElements, final SessionContext sessionContext) {
BsonDocument readConcernDocument = getReadConcernDocument(sessionContext, getSettings().getMaxWireVersion());
if (!readConcernDocument.isEmpty()) {
extraElements.add(new BsonElement("readConcern", readConcernDocument));
}
}
private static OpCode getOpCode(final MessageSettings settings, final ClusterConnectionMode clusterConnectionMode,
@Nullable final ServerApi serverApi) {
return isServerVersionAtLeastThreeDotSix(settings) || clusterConnectionMode == LOAD_BALANCED || serverApi != null
? OpCode.OP_MSG
: OpCode.OP_QUERY;
}
private static boolean isServerVersionAtLeastThreeDotSix(final MessageSettings settings) {
return settings.getMaxWireVersion() >= THREE_DOT_SIX_WIRE_VERSION;
}
}