com.mongodb.internal.operation.DropCollectionOperation Maven / Gradle / Ivy
Show all versions of mongodb-driver-core Show documentation
/*
* 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.operation;
import com.mongodb.MongoCommandException;
import com.mongodb.MongoNamespace;
import com.mongodb.WriteConcern;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncReadWriteBinding;
import com.mongodb.internal.binding.AsyncWriteBinding;
import com.mongodb.internal.binding.ReadWriteBinding;
import com.mongodb.internal.binding.WriteBinding;
import com.mongodb.internal.connection.AsyncConnection;
import com.mongodb.lang.Nullable;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.codecs.BsonValueCodec;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.function.Supplier;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback;
import static com.mongodb.internal.operation.AsyncOperationHelper.executeCommandAsync;
import static com.mongodb.internal.operation.AsyncOperationHelper.releasingCallback;
import static com.mongodb.internal.operation.AsyncOperationHelper.withAsyncConnection;
import static com.mongodb.internal.operation.AsyncOperationHelper.writeConcernErrorTransformerAsync;
import static com.mongodb.internal.operation.CommandOperationHelper.isNamespaceError;
import static com.mongodb.internal.operation.CommandOperationHelper.rethrowIfNotNamespaceError;
import static com.mongodb.internal.operation.OperationHelper.LOGGER;
import static com.mongodb.internal.operation.SyncOperationHelper.executeCommand;
import static com.mongodb.internal.operation.SyncOperationHelper.withConnection;
import static com.mongodb.internal.operation.SyncOperationHelper.writeConcernErrorTransformer;
import static com.mongodb.internal.operation.WriteConcernHelper.appendWriteConcernToCommand;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
/**
* Operation to drop a Collection in MongoDB. The {@code execute} method throws MongoCommandFailureException if something goes wrong, but
* it will not throw an Exception if the collection does not exist before trying to drop it.
*
* This class is not part of the public API and may be removed or changed at any time
*/
public class DropCollectionOperation implements AsyncWriteOperation, WriteOperation {
private static final String ENCRYPT_PREFIX = "enxcol_.";
private static final BsonValueCodec BSON_VALUE_CODEC = new BsonValueCodec();
private final MongoNamespace namespace;
private final WriteConcern writeConcern;
private BsonDocument encryptedFields;
private boolean autoEncryptedFields;
public DropCollectionOperation(final MongoNamespace namespace) {
this(namespace, null);
}
public DropCollectionOperation(final MongoNamespace namespace, @Nullable final WriteConcern writeConcern) {
this.namespace = notNull("namespace", namespace);
this.writeConcern = writeConcern;
}
public WriteConcern getWriteConcern() {
return writeConcern;
}
public DropCollectionOperation encryptedFields(final BsonDocument encryptedFields) {
this.encryptedFields = encryptedFields;
return this;
}
public DropCollectionOperation autoEncryptedFields(final boolean autoEncryptedFields) {
this.autoEncryptedFields = autoEncryptedFields;
return this;
}
@Override
public Void execute(final WriteBinding binding) {
BsonDocument localEncryptedFields = getEncryptedFields((ReadWriteBinding) binding);
return withConnection(binding, connection -> {
getCommands(localEncryptedFields).forEach(command -> {
try {
executeCommand(binding, namespace.getDatabaseName(), command.get(),
connection, writeConcernErrorTransformer());
} catch (MongoCommandException e) {
rethrowIfNotNamespaceError(e);
}
});
return null;
});
}
@Override
public void executeAsync(final AsyncWriteBinding binding, final SingleResultCallback callback) {
SingleResultCallback errHandlingCallback = errorHandlingCallback(callback, LOGGER);
getEncryptedFields((AsyncReadWriteBinding) binding, (result, t) -> {
if (t != null) {
errHandlingCallback.onResult(null, t);
} else {
withAsyncConnection(binding, (connection, t1) -> {
if (t1 != null) {
errHandlingCallback.onResult(null, t1);
} else {
new ProcessCommandsCallback(binding, connection, getCommands(result), releasingCallback(errHandlingCallback,
connection))
.onResult(null, null);
}
});
}
});
}
/**
* With Queryable Encryption dropping a collection can involve more logic and commands.
*
*
* A call to a driver helper Collection.drop(dropOptions) must check if the collection namespace (.)
* has an associated encryptedFields. Check for an associated encryptedFields from the following:
*
* - The encryptedFields option passed in dropOptions.
* - The value of AutoEncryptionOpts.encryptedFieldsMap[
.].
* - If AutoEncryptionOpts.encryptedFieldsMap is not null, run a listCollections command on the database databaseName with the
* filter { "name": "
" }. Check the returned options for the encryptedFields option.
*
*
*
* If the collection namespace has an associated encryptedFields, then do the following operations.
* If any of the following operations error, the remaining operations are not attempted:
*
* - Drop the collection collectionName.
*
- Drop the collection with name encryptedFields["escCollection"].
* If encryptedFields["escCollection"] is not set, use the collection name enxcol_.
.esc.
* - Drop the collection with name encryptedFields["ecocCollection"].
* If encryptedFields["ecocCollection"] is not set, use the collection name enxcol_.
.ecoc.
*
*
*
* @return the list of commands to run to create the collection
*/
private List> getCommands(final BsonDocument encryptedFields) {
if (encryptedFields == null) {
return singletonList(this::dropCollectionCommand);
} else {
return asList(
() -> getDropEncryptedFieldsCollectionCommand(encryptedFields, "esc"),
() -> getDropEncryptedFieldsCollectionCommand(encryptedFields, "ecoc"),
this::dropCollectionCommand
);
}
}
private BsonDocument getDropEncryptedFieldsCollectionCommand(final BsonDocument encryptedFields, final String collectionSuffix) {
BsonString defaultCollectionName = new BsonString(ENCRYPT_PREFIX + namespace.getCollectionName() + "." + collectionSuffix);
return new BsonDocument("drop", encryptedFields.getOrDefault(collectionSuffix + "Collection", defaultCollectionName));
}
private BsonDocument dropCollectionCommand() {
BsonDocument commandDocument = new BsonDocument("drop", new BsonString(namespace.getCollectionName()));
appendWriteConcernToCommand(writeConcern, commandDocument);
return commandDocument;
}
@Nullable
private BsonDocument getEncryptedFields(final ReadWriteBinding readWriteBinding) {
if (encryptedFields == null && autoEncryptedFields) {
try (BatchCursor cursor = listCollectionOperation().execute(readWriteBinding)) {
return getCollectionEncryptedFields(encryptedFields, cursor.tryNext());
}
}
return encryptedFields;
}
private void getEncryptedFields(
final AsyncReadWriteBinding asyncReadWriteBinding,
final SingleResultCallback callback) {
if (encryptedFields == null && autoEncryptedFields) {
listCollectionOperation().executeAsync(asyncReadWriteBinding, (cursor, t) -> {
if (t != null) {
callback.onResult(null, t);
} else {
cursor.next((bsonValues, t1) -> {
if (t1 != null) {
callback.onResult(null, t1);
} else {
callback.onResult(getCollectionEncryptedFields(encryptedFields, bsonValues), null);
}
});
}
});
} else {
callback.onResult(encryptedFields, null);
}
}
private BsonDocument getCollectionEncryptedFields(final BsonDocument defaultEncryptedFields,
@Nullable final List bsonValues) {
if (bsonValues != null && bsonValues.size() > 0) {
return bsonValues.get(0).asDocument()
.getDocument("options", new BsonDocument())
.getDocument("encryptedFields", new BsonDocument());
}
return defaultEncryptedFields;
}
private ListCollectionsOperation listCollectionOperation() {
return new ListCollectionsOperation<>(namespace.getDatabaseName(), BSON_VALUE_CODEC)
.filter(new BsonDocument("name", new BsonString(namespace.getCollectionName())))
.batchSize(1);
}
/**
* A SingleResultCallback that can be repeatedly called via onResult until all commands have been run.
*/
class ProcessCommandsCallback implements SingleResultCallback {
private final AsyncWriteBinding binding;
private final AsyncConnection connection;
private final SingleResultCallback finalCallback;
private final Deque> commands;
ProcessCommandsCallback(
final AsyncWriteBinding binding, final AsyncConnection connection,
final List> commands,
final SingleResultCallback finalCallback) {
this.binding = binding;
this.connection = connection;
this.finalCallback = finalCallback;
this.commands = new ArrayDeque<>(commands);
}
@Override
public void onResult(@Nullable final Void result, @Nullable final Throwable t) {
if (t != null && !isNamespaceError(t)) {
finalCallback.onResult(null, t);
return;
}
Supplier nextCommandFunction = commands.poll();
if (nextCommandFunction == null) {
finalCallback.onResult(null, null);
} else {
executeCommandAsync(binding, namespace.getDatabaseName(), nextCommandFunction.get(),
connection, writeConcernErrorTransformerAsync(), this);
}
}
}
}