io.vertx.ext.mongo.impl.MongoClientImpl.java Source code

Java tutorial

Introduction

Here is the source code for io.vertx.ext.mongo.impl.MongoClientImpl.java

Source

/*
 * Copyright 2014 Red Hat, Inc.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */

package io.vertx.ext.mongo.impl;

import com.mongodb.Block;
import com.mongodb.WriteConcern;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.DistinctIterable;
import com.mongodb.async.client.FindIterable;
import com.mongodb.async.client.ListIndexesIterable;
import com.mongodb.async.client.MongoClients;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.async.client.MongoDatabase;
import com.mongodb.async.client.MongoIterable;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndReplaceOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReturnDocument;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;

import org.bson.BsonDocument;
import org.bson.BsonDocumentReader;
import org.bson.BsonValue;
import org.bson.codecs.DecoderContext;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.core.shareddata.Shareable;
import io.vertx.ext.mongo.FindOptions;
import io.vertx.ext.mongo.IndexOptions;
import io.vertx.ext.mongo.MongoClient;
import io.vertx.ext.mongo.MongoClientDeleteResult;
import io.vertx.ext.mongo.MongoClientUpdateResult;
import io.vertx.ext.mongo.UpdateOptions;
import io.vertx.ext.mongo.WriteOption;
import io.vertx.ext.mongo.impl.codec.json.JsonObjectCodec;
import io.vertx.ext.mongo.impl.config.MongoClientOptionsParser;

import static java.util.Objects.requireNonNull;

/**
 * The implementation of the {@link io.vertx.ext.mongo.MongoClient}. This implementation is based on the async driver
 * provided by Mongo.
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class MongoClientImpl implements io.vertx.ext.mongo.MongoClient {

    private static final Logger log = LoggerFactory.getLogger(MongoClientImpl.class);

    private static final UpdateOptions DEFAULT_UPDATE_OPTIONS = new UpdateOptions();
    private static final FindOptions DEFAULT_FIND_OPTIONS = new FindOptions();
    private static final String ID_FIELD = "_id";

    private static final String DS_LOCAL_MAP_NAME = "__vertx.MongoClient.datasources";

    private final Vertx vertx;
    protected com.mongodb.async.client.MongoClient mongo;
    protected final MongoHolder holder;
    protected boolean useObjectId;

    public MongoClientImpl(Vertx vertx, JsonObject config, String dataSourceName) {
        Objects.requireNonNull(vertx);
        Objects.requireNonNull(config);
        Objects.requireNonNull(dataSourceName);
        this.vertx = vertx;
        this.holder = lookupHolder(dataSourceName, config);
        this.mongo = holder.mongo();
        this.useObjectId = config.getBoolean("useObjectId", false);
    }

    @Override
    public void close() {
        holder.close();
    }

    @Override
    public io.vertx.ext.mongo.MongoClient save(String collection, JsonObject document,
            Handler<AsyncResult<String>> resultHandler) {
        saveWithOptions(collection, document, null, resultHandler);
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient saveWithOptions(String collection, JsonObject document,
            WriteOption writeOption, Handler<AsyncResult<String>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(document, "document cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        MongoCollection<JsonObject> coll = getCollection(collection, writeOption);
        Object id = document.getValue(ID_FIELD);
        if (id == null) {
            coll.insertOne(document,
                    convertCallback(resultHandler,
                            wr -> useObjectId
                                    ? document.getJsonObject(ID_FIELD).getString(JsonObjectCodec.OID_FIELD)
                                    : document.getString(ID_FIELD)));
        } else {
            JsonObject filter = new JsonObject();
            JsonObject encodedDocument = encodeKeyWhenUseObjectId(document);
            filter.put(ID_FIELD, encodedDocument.getValue(ID_FIELD));

            com.mongodb.client.model.UpdateOptions updateOptions = new com.mongodb.client.model.UpdateOptions()
                    .upsert(true);

            coll.replaceOne(wrap(filter), encodedDocument, updateOptions,
                    convertCallback(resultHandler, result -> null));
        }
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient insert(String collection, JsonObject document,
            Handler<AsyncResult<String>> resultHandler) {
        insertWithOptions(collection, document, null, resultHandler);
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient insertWithOptions(String collection, JsonObject document,
            WriteOption writeOption, Handler<AsyncResult<String>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(document, "document cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        JsonObject encodedDocument = encodeKeyWhenUseObjectId(document);
        boolean hasCustomId = document.containsKey(ID_FIELD);

        MongoCollection<JsonObject> coll = getCollection(collection, writeOption);
        coll.insertOne(encodedDocument, convertCallback(resultHandler, wr -> {
            if (hasCustomId)
                return null;

            JsonObject decodedDocument = decodeKeyWhenUseObjectId(encodedDocument);
            return decodedDocument.getString(ID_FIELD);
        }));
        return this;
    }

    @Deprecated
    @Override
    public io.vertx.ext.mongo.MongoClient update(String collection, JsonObject query, JsonObject update,
            Handler<AsyncResult<Void>> resultHandler) {
        updateWithOptions(collection, query, update, DEFAULT_UPDATE_OPTIONS, resultHandler);
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient updateCollection(String collection, JsonObject query, JsonObject update,
            Handler<AsyncResult<MongoClientUpdateResult>> resultHandler) {
        updateCollectionWithOptions(collection, query, update, DEFAULT_UPDATE_OPTIONS, resultHandler);
        return this;
    }

    @Deprecated
    @Override
    public io.vertx.ext.mongo.MongoClient updateWithOptions(String collection, JsonObject query, JsonObject update,
            UpdateOptions options, Handler<AsyncResult<Void>> resultHandler) {
        updateCollectionWithOptions(collection, query, update, options, toVoidAsyncResult(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient updateCollectionWithOptions(String collection, JsonObject query,
            JsonObject update, UpdateOptions options, Handler<AsyncResult<MongoClientUpdateResult>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(update, "update cannot be null");
        requireNonNull(options, "options cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        MongoCollection<JsonObject> coll = getCollection(collection, options.getWriteOption());
        Bson bquery = wrap(encodeKeyWhenUseObjectId(query));
        Bson bupdate = wrap(encodeKeyWhenUseObjectId(update));
        if (options.isMulti()) {
            coll.updateMany(bquery, bupdate, mongoUpdateOptions(options), toMongoClientUpdateResult(resultHandler));
        } else {
            coll.updateOne(bquery, bupdate, mongoUpdateOptions(options), toMongoClientUpdateResult(resultHandler));
        }
        return this;
    }

    @Deprecated
    @Override
    public io.vertx.ext.mongo.MongoClient replace(String collection, JsonObject query, JsonObject replace,
            Handler<AsyncResult<Void>> resultHandler) {
        replaceWithOptions(collection, query, replace, DEFAULT_UPDATE_OPTIONS, resultHandler);
        return this;
    }

    @Override
    public MongoClient replaceDocuments(String collection, JsonObject query, JsonObject replace,
            Handler<AsyncResult<MongoClientUpdateResult>> resultHandler) {
        replaceDocumentsWithOptions(collection, query, replace, DEFAULT_UPDATE_OPTIONS, resultHandler);
        return this;
    }

    @Deprecated
    @Override
    public io.vertx.ext.mongo.MongoClient replaceWithOptions(String collection, JsonObject query,
            JsonObject replace, UpdateOptions options, Handler<AsyncResult<Void>> resultHandler) {
        return replaceDocumentsWithOptions(collection, query, replace, options, toVoidAsyncResult(resultHandler));
    }

    @Override
    public MongoClient replaceDocumentsWithOptions(String collection, JsonObject query, JsonObject replace,
            UpdateOptions options, Handler<AsyncResult<MongoClientUpdateResult>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(replace, "update cannot be null");
        requireNonNull(options, "options cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        MongoCollection<JsonObject> coll = getCollection(collection, options.getWriteOption());
        Bson bquery = wrap(encodeKeyWhenUseObjectId(query));
        coll.replaceOne(bquery, encodeKeyWhenUseObjectId(replace), mongoUpdateOptions(options),
                toMongoClientUpdateResult(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient find(String collection, JsonObject query,
            Handler<AsyncResult<List<JsonObject>>> resultHandler) {
        findWithOptions(collection, query, DEFAULT_FIND_OPTIONS, resultHandler);
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findBatch(String collection, JsonObject query,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        findBatchWithOptions(collection, query, DEFAULT_FIND_OPTIONS, resultHandler);
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findWithOptions(String collection, JsonObject query, FindOptions options,
            Handler<AsyncResult<List<JsonObject>>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        FindIterable<JsonObject> view = doFind(collection, encodeKeyWhenUseObjectId(query), options);
        List<JsonObject> results = new ArrayList<>();
        view.into(results, convertCallback(resultHandler, wr -> {
            results.forEach(this::decodeKeyWhenUseObjectId);
            return results;
        }));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findBatchWithOptions(String collection, JsonObject query,
            FindOptions options, Handler<AsyncResult<JsonObject>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        FindIterable<JsonObject> view = doFind(collection, query, options);
        Block<JsonObject> documentBlock = document -> resultHandler.handle(Future.succeededFuture(document));
        SingleResultCallback<Void> callbackWhenFinished = (result, throwable) -> {
            if (throwable != null) {
                resultHandler.handle(Future.failedFuture(throwable));
            } else {
                resultHandler.handle(Future.succeededFuture());
            }
        };
        vertx.runOnContext(v -> view.forEach(documentBlock, callbackWhenFinished));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findOne(String collection, JsonObject query, JsonObject fields,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        JsonObject encodedQuery = encodeKeyWhenUseObjectId(query);

        Bson bquery = wrap(encodedQuery);
        Bson bfields = wrap(fields);
        getCollection(collection).find(bquery).projection(bfields).first(wrapCallback(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findOneAndUpdate(String collection, JsonObject query, JsonObject update,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        findOneAndUpdateWithOptions(collection, query, update, DEFAULT_FIND_OPTIONS, DEFAULT_UPDATE_OPTIONS,
                resultHandler);
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findOneAndUpdateWithOptions(String collection, JsonObject query,
            JsonObject update, FindOptions findOptions, UpdateOptions updateOptions,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(findOptions, "find options cannot be null");
        requireNonNull(updateOptions, "update options cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        JsonObject encodedQuery = encodeKeyWhenUseObjectId(query);

        Bson bquery = wrap(encodedQuery);
        Bson bupdate = wrap(update);
        FindOneAndUpdateOptions foauOptions = new FindOneAndUpdateOptions();
        foauOptions.sort(wrap(findOptions.getSort()));
        foauOptions.projection(wrap(findOptions.getFields()));
        foauOptions.upsert(updateOptions.isUpsert());
        foauOptions.returnDocument(
                updateOptions.isReturningNewDocument() ? ReturnDocument.AFTER : ReturnDocument.BEFORE);

        MongoCollection<JsonObject> coll = getCollection(collection);
        coll.findOneAndUpdate(bquery, bupdate, foauOptions, wrapCallback(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findOneAndReplace(String collection, JsonObject query, JsonObject update,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        findOneAndReplaceWithOptions(collection, query, update, DEFAULT_FIND_OPTIONS, DEFAULT_UPDATE_OPTIONS,
                resultHandler);
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findOneAndReplaceWithOptions(String collection, JsonObject query,
            JsonObject replace, FindOptions findOptions, UpdateOptions updateOptions,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(findOptions, "find options cannot be null");
        requireNonNull(updateOptions, "update options cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        JsonObject encodedQuery = encodeKeyWhenUseObjectId(query);

        Bson bquery = wrap(encodedQuery);
        FindOneAndReplaceOptions foarOptions = new FindOneAndReplaceOptions();
        foarOptions.sort(wrap(findOptions.getSort()));
        foarOptions.projection(wrap(findOptions.getFields()));
        foarOptions.upsert(updateOptions.isUpsert());
        foarOptions.returnDocument(
                updateOptions.isReturningNewDocument() ? ReturnDocument.AFTER : ReturnDocument.BEFORE);

        MongoCollection<JsonObject> coll = getCollection(collection);
        coll.findOneAndReplace(bquery, replace, foarOptions, wrapCallback(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findOneAndDelete(String collection, JsonObject query,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        findOneAndDeleteWithOptions(collection, query, DEFAULT_FIND_OPTIONS, resultHandler);
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient findOneAndDeleteWithOptions(String collection, JsonObject query,
            FindOptions findOptions, Handler<AsyncResult<JsonObject>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(findOptions, "find options cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        JsonObject encodedQuery = encodeKeyWhenUseObjectId(query);

        Bson bquery = wrap(encodedQuery);
        FindOneAndDeleteOptions foadOptions = new FindOneAndDeleteOptions();
        foadOptions.sort(wrap(findOptions.getSort()));
        foadOptions.projection(wrap(findOptions.getFields()));

        MongoCollection<JsonObject> coll = getCollection(collection);
        coll.findOneAndDelete(bquery, foadOptions, wrapCallback(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient count(String collection, JsonObject query,
            Handler<AsyncResult<Long>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        Bson bquery = wrap(encodeKeyWhenUseObjectId(query));
        MongoCollection<JsonObject> coll = getCollection(collection);
        coll.count(bquery, wrapCallback(resultHandler));
        return this;
    }

    @Deprecated
    @Override
    public io.vertx.ext.mongo.MongoClient remove(String collection, JsonObject query,
            Handler<AsyncResult<Void>> resultHandler) {
        removeWithOptions(collection, query, null, resultHandler);
        return this;
    }

    @Override
    public MongoClient removeDocuments(String collection, JsonObject query,
            Handler<AsyncResult<MongoClientDeleteResult>> resultHandler) {
        removeDocumentsWithOptions(collection, query, null, resultHandler);
        return this;
    }

    @Deprecated
    @Override
    public io.vertx.ext.mongo.MongoClient removeWithOptions(String collection, JsonObject query,
            WriteOption writeOption, Handler<AsyncResult<Void>> resultHandler) {
        removeDocumentsWithOptions(collection, query, writeOption, toVoidAsyncResult(resultHandler));
        return this;
    }

    @Override
    public MongoClient removeDocumentsWithOptions(String collection, JsonObject query, WriteOption writeOption,
            Handler<AsyncResult<MongoClientDeleteResult>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        MongoCollection<JsonObject> coll = getCollection(collection, writeOption);
        Bson bquery = wrap(encodeKeyWhenUseObjectId(query));
        coll.deleteMany(bquery, toMongoClientDeleteResult(resultHandler));
        return this;
    }

    @Deprecated
    @Override
    public io.vertx.ext.mongo.MongoClient removeOne(String collection, JsonObject query,
            Handler<AsyncResult<Void>> resultHandler) {
        removeOneWithOptions(collection, query, null, resultHandler);
        return this;
    }

    @Override
    public MongoClient removeDocument(String collection, JsonObject query,
            Handler<AsyncResult<MongoClientDeleteResult>> resultHandler) {
        removeDocumentWithOptions(collection, query, null, resultHandler);
        return this;
    }

    @Deprecated
    @Override
    public io.vertx.ext.mongo.MongoClient removeOneWithOptions(String collection, JsonObject query,
            WriteOption writeOption, Handler<AsyncResult<Void>> resultHandler) {
        removeDocumentWithOptions(collection, query, writeOption, toVoidAsyncResult(resultHandler));
        return this;
    }

    @Override
    public MongoClient removeDocumentWithOptions(String collection, JsonObject query, WriteOption writeOption,
            Handler<AsyncResult<MongoClientDeleteResult>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(query, "query cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        MongoCollection<JsonObject> coll = getCollection(collection, writeOption);
        Bson bquery = wrap(encodeKeyWhenUseObjectId(query));
        coll.deleteOne(bquery, toMongoClientDeleteResult(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient createCollection(String collection,
            Handler<AsyncResult<Void>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        holder.db.createCollection(collection, wrapCallback(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient getCollections(Handler<AsyncResult<List<String>>> resultHandler) {
        requireNonNull(resultHandler, "resultHandler cannot be null");
        List<String> names = new ArrayList<>();
        Context context = vertx.getOrCreateContext();
        holder.db.listCollectionNames().into(names, (res, error) -> {
            context.runOnContext(v -> {
                if (error != null) {
                    resultHandler.handle(Future.failedFuture(error));
                } else {
                    resultHandler.handle(Future.succeededFuture(names));
                }
            });
        });
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient dropCollection(String collection,
            Handler<AsyncResult<Void>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        MongoCollection<JsonObject> coll = getCollection(collection);
        coll.drop(wrapCallback(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient createIndex(String collection, JsonObject key,
            Handler<AsyncResult<Void>> resultHandler) {
        return createIndexWithOptions(collection, key, new IndexOptions(), resultHandler);
    }

    @Override
    public io.vertx.ext.mongo.MongoClient createIndexWithOptions(String collection, JsonObject key,
            IndexOptions options, Handler<AsyncResult<Void>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(key, "fieldName cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");
        MongoCollection<JsonObject> coll = getCollection(collection);
        com.mongodb.client.model.IndexOptions driverOpts = new com.mongodb.client.model.IndexOptions()
                .background(options.isBackground()).unique(options.isUnique()).name(options.getName())
                .sparse(options.isSparse()).expireAfter(options.getExpireAfter(TimeUnit.SECONDS), TimeUnit.SECONDS)
                .version(options.getVersion()).weights(toBson(options.getWeights()))
                .defaultLanguage(options.getDefaultLanguage()).languageOverride(options.getLanguageOverride())
                .textVersion(options.getTextVersion()).sphereVersion(options.getSphereVersion())
                .bits(options.getBits()).min(options.getMin()).max(options.getMax())
                .bucketSize(options.getBucketSize()).storageEngine(toBson(options.getStorageEngine()))
                .partialFilterExpression(toBson(options.getPartialFilterExpression()));
        coll.createIndex(wrap(key), driverOpts, wrapCallback(toVoidAsyncResult(resultHandler)));
        return this;
    }

    private static Bson toBson(JsonObject json) {
        return json == null ? null : BsonDocument.parse(json.encode());
    }

    @Override
    public io.vertx.ext.mongo.MongoClient listIndexes(String collection,
            Handler<AsyncResult<JsonArray>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");
        MongoCollection<JsonObject> coll = getCollection(collection);
        ListIndexesIterable indexes = coll.listIndexes(JsonObject.class);
        if (indexes != null) {
            convertMongoIterable(indexes, resultHandler);
        }
        return this;
    }

    @Override
    public MongoClient dropIndex(String collection, String indexName, Handler<AsyncResult<Void>> resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(indexName, "indexName cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");
        MongoCollection<JsonObject> coll = getCollection(collection);
        coll.dropIndex(indexName, wrapCallback(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient runCommand(String commandName, JsonObject command,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        requireNonNull(commandName, "commandName cannot be null");
        requireNonNull(command, "command cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");
        // The command name must be the first entry in the bson, so to ensure this we must recreate and add the command
        // name as first (JsonObject is internally ordered)
        JsonObject json = new JsonObject();
        Object commandVal = command.getValue(commandName);
        if (commandVal == null) {
            throw new IllegalArgumentException("commandBody does not contain key for " + commandName);
        }
        json.put(commandName, commandVal);
        command.forEach(entry -> {
            if (!entry.getKey().equals(commandName)) {
                json.put(entry.getKey(), entry.getValue());
            }
        });

        holder.db.runCommand(wrap(json), JsonObject.class, wrapCallback(resultHandler));
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient distinct(String collection, String fieldName, String resultClassname,
            Handler<AsyncResult<JsonArray>> resultHandler) {
        DistinctIterable distinctValues = findDistinctValues(collection, fieldName, resultClassname, resultHandler);

        if (distinctValues != null) {
            convertMongoIterable(distinctValues, resultHandler);
        }
        return this;
    }

    @Override
    public io.vertx.ext.mongo.MongoClient distinctBatch(String collection, String fieldName, String resultClassname,
            Handler<AsyncResult<JsonObject>> resultHandler) {
        DistinctIterable distinctValues = findDistinctValues(collection, fieldName, resultClassname, resultHandler);

        if (distinctValues != null) {
            Context context = vertx.getOrCreateContext();
            Block valueBlock = value -> {
                context.runOnContext(v -> {
                    Map mapValue = new HashMap();
                    mapValue.put(fieldName, value);
                    resultHandler.handle(Future.succeededFuture(new JsonObject(mapValue)));
                });
            };
            SingleResultCallback<Void> callbackWhenFinished = (result, throwable) -> {
                if (throwable != null) {
                    resultHandler.handle(Future.failedFuture(throwable));
                }
            };
            try {
                distinctValues.forEach(valueBlock, callbackWhenFinished);
            } catch (Exception unhandledEx) {
                resultHandler.handle(Future.failedFuture(unhandledEx));
            }
        }
        return this;
    }

    private void convertMongoIterable(MongoIterable iterable, Handler<AsyncResult<JsonArray>> resultHandler) {
        List results = new ArrayList();
        try {
            Context context = vertx.getOrCreateContext();
            iterable.into(results, (result, throwable) -> {
                context.runOnContext(v -> {
                    if (throwable != null) {
                        resultHandler.handle(Future.failedFuture(throwable));
                    } else {
                        resultHandler.handle(Future.succeededFuture(new JsonArray((List) result)));
                    }
                });
            });
        } catch (Exception unhandledEx) {
            resultHandler.handle(Future.failedFuture(unhandledEx));
        }

    }

    private DistinctIterable findDistinctValues(String collection, String fieldName, String resultClassname,
            Handler resultHandler) {
        requireNonNull(collection, "collection cannot be null");
        requireNonNull(fieldName, "fieldName cannot be null");
        requireNonNull(resultHandler, "resultHandler cannot be null");

        final Class resultClass;
        try {
            resultClass = Class.forName(resultClassname);
        } catch (ClassNotFoundException e) {
            resultHandler.handle(Future.failedFuture(e));
            return null;
        }
        MongoCollection<JsonObject> mongoCollection = getCollection(collection);
        return mongoCollection.distinct(fieldName, resultClass);
    }

    private JsonObject encodeKeyWhenUseObjectId(JsonObject json) {
        if (!useObjectId)
            return json;

        Object idString = json.getValue(ID_FIELD, null);
        if (idString instanceof String && ObjectId.isValid((String) idString)) {
            json.put(ID_FIELD, new JsonObject().put(JsonObjectCodec.OID_FIELD, idString));
        }

        return json;
    }

    private JsonObject decodeKeyWhenUseObjectId(JsonObject json) {
        if (!useObjectId)
            return json;

        Object idField = json.getValue(ID_FIELD, null);
        if (!(idField instanceof JsonObject))
            return json;

        Object idString = ((JsonObject) idField).getValue(JsonObjectCodec.OID_FIELD, null);
        if (!(idString instanceof String))
            return json;

        json.put(ID_FIELD, (String) idString);

        return json;
    }

    private <T, R> SingleResultCallback<T> convertCallback(Handler<AsyncResult<R>> resultHandler,
            Function<T, R> converter) {
        Context context = vertx.getOrCreateContext();
        return (result, error) -> {
            context.runOnContext(v -> {
                if (error != null) {
                    resultHandler.handle(Future.failedFuture(error));
                } else {
                    resultHandler.handle(Future.succeededFuture(converter.apply(result)));
                }
            });
        };
    }

    private <T> Handler<AsyncResult<T>> toVoidAsyncResult(Handler<AsyncResult<Void>> resultHandler) {
        return result -> {
            if (result.succeeded()) {
                resultHandler.handle(Future.succeededFuture(null));
            } else {
                resultHandler.handle(Future.failedFuture(result.cause()));
            }
        };
    }

    private SingleResultCallback<UpdateResult> toMongoClientUpdateResult(
            Handler<AsyncResult<MongoClientUpdateResult>> resultHandler) {
        return convertCallback(resultHandler, result -> {
            if (result.wasAcknowledged()) {
                return convertToMongoClientUpdateResult(result.getMatchedCount(), result.getUpsertedId(),
                        result.getModifiedCount());
            } else {
                return null;
            }
        });
    }

    private SingleResultCallback<DeleteResult> toMongoClientDeleteResult(
            Handler<AsyncResult<MongoClientDeleteResult>> resultHandler) {
        return convertCallback(resultHandler, result -> {
            if (result.wasAcknowledged()) {
                return new MongoClientDeleteResult(result.getDeletedCount());
            } else {
                return null;
            }
        });
    }

    private <T> SingleResultCallback<T> wrapCallback(Handler<AsyncResult<T>> resultHandler) {
        Context context = vertx.getOrCreateContext();
        return (result, error) -> {
            context.runOnContext(v -> {
                if (error != null) {
                    resultHandler.handle(Future.failedFuture(error));
                } else {
                    resultHandler.handle(Future.succeededFuture(result));
                }
            });
        };
    }

    private FindIterable<JsonObject> doFind(String collection, JsonObject query, FindOptions options) {
        return doFind(collection, null, query, options);
    }

    private FindIterable<JsonObject> doFind(String collection, WriteOption writeOption, JsonObject query,
            FindOptions options) {
        MongoCollection<JsonObject> coll = getCollection(collection, writeOption);
        Bson bquery = wrap(encodeKeyWhenUseObjectId(query));
        FindIterable<JsonObject> find = coll.find(bquery, JsonObject.class);
        if (options.getLimit() != -1) {
            find.limit(options.getLimit());
        }
        if (options.getSkip() > 0) {
            find.skip(options.getSkip());
        }
        if (options.getSort() != null) {
            find.sort(wrap(options.getSort()));
        }
        if (options.getFields() != null) {
            find.projection(wrap(options.getFields()));
        }
        return find;
    }

    private MongoCollection<JsonObject> getCollection(String name) {
        return getCollection(name, null);
    }

    private MongoCollection<JsonObject> getCollection(String name, WriteOption writeOption) {
        MongoCollection<JsonObject> coll = holder.db.getCollection(name, JsonObject.class);
        if (coll != null && writeOption != null) {
            coll = coll.withWriteConcern(WriteConcern.valueOf(writeOption.name()));
        }
        return coll;
    }

    private static com.mongodb.client.model.UpdateOptions mongoUpdateOptions(UpdateOptions options) {
        return new com.mongodb.client.model.UpdateOptions().upsert(options.isUpsert());
    }

    private JsonObjectBsonAdapter wrap(JsonObject jsonObject) {
        return jsonObject == null ? null : new JsonObjectBsonAdapter(jsonObject);
    }

    private void removeFromMap(LocalMap<String, MongoHolder> map, String dataSourceName) {
        synchronized (vertx) {
            map.remove(dataSourceName);
            if (map.isEmpty()) {
                map.close();
            }
        }
    }

    private MongoHolder lookupHolder(String datasourceName, JsonObject config) {
        synchronized (vertx) {
            LocalMap<String, MongoHolder> map = vertx.sharedData().getLocalMap(DS_LOCAL_MAP_NAME);
            MongoHolder theHolder = map.get(datasourceName);
            if (theHolder == null) {
                theHolder = new MongoHolder(config, () -> removeFromMap(map, datasourceName));
                map.put(datasourceName, theHolder);
            } else {
                theHolder.incRefCount();
            }
            return theHolder;
        }
    }

    private MongoClientUpdateResult convertToMongoClientUpdateResult(long docMatched, BsonValue upsertId,
            long docModified) {
        JsonObject jsonUpsertId;
        if (upsertId != null) {
            JsonObjectCodec jsonObjectCodec = new JsonObjectCodec(new JsonObject());

            BsonDocument upsertIdDocument = new BsonDocument();
            upsertIdDocument.append(MongoClientUpdateResult.ID_FIELD, upsertId);

            BsonDocumentReader bsonDocumentReader = new BsonDocumentReader(upsertIdDocument);
            jsonUpsertId = jsonObjectCodec.decode(bsonDocumentReader, DecoderContext.builder().build());
        } else {
            jsonUpsertId = null;
        }

        return new MongoClientUpdateResult(docMatched, jsonUpsertId, docModified);
    }

    private static class MongoHolder implements Shareable {
        com.mongodb.async.client.MongoClient mongo;
        MongoDatabase db;
        JsonObject config;
        Runnable closeRunner;
        int refCount = 1;

        public MongoHolder(JsonObject config, Runnable closeRunner) {
            this.config = config;
            this.closeRunner = closeRunner;
        }

        synchronized com.mongodb.async.client.MongoClient mongo() {
            if (mongo == null) {
                MongoClientOptionsParser parser = new MongoClientOptionsParser(config);
                mongo = MongoClients.create(parser.settings());
                db = mongo.getDatabase(parser.database());
            }
            return mongo;
        }

        synchronized void incRefCount() {
            refCount++;
        }

        synchronized void close() {
            if (--refCount == 0) {
                if (mongo != null) {
                    mongo.close();
                }
                if (closeRunner != null) {
                    closeRunner.run();
                }
            }
        }
    }

}