com.ait.tooling.server.mongodb.MongoDB.java Source code

Java tutorial

Introduction

Here is the source code for com.ait.tooling.server.mongodb.MongoDB.java

Source

/*
 * Copyright (c) 2017 Ahome' Innovation Technologies. All rights reserved.
 *
 * 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.ait.tooling.server.mongodb;

import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Filters.exists;
import static com.mongodb.client.model.Filters.gt;
import static com.mongodb.client.model.Filters.gte;
import static com.mongodb.client.model.Filters.in;
import static com.mongodb.client.model.Filters.lt;
import static com.mongodb.client.model.Filters.lte;
import static com.mongodb.client.model.Filters.ne;
import static com.mongodb.client.model.Filters.nin;
import static com.mongodb.client.model.Filters.not;
import static com.mongodb.client.model.Filters.or;

import java.io.Closeable;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.log4j.Logger;
import org.bson.BSON;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.Transformer;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

import com.ait.tooling.common.api.java.util.StringOps;
import com.ait.tooling.server.core.json.JSONUtils;
import com.ait.tooling.server.mongodb.support.spring.IMongoDBCollectionOptions;
import com.ait.tooling.server.mongodb.support.spring.IMongoDBOptions;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.FindIterable;
import com.mongodb.client.ListIndexesIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.UpdateOptions;

public final class MongoDB {
    private static final Logger logger = Logger.getLogger(MongoDB.class);

    private final MongoClient m_mongo;

    private final String m_usedb;

    private final boolean m_useid;

    private final Map<String, IMongoDBOptions> m_dbops;

    @SuppressWarnings("unchecked")
    private static final Map<String, Object> CAST_MAP(Map<String, ?> map) {
        return (Map<String, Object>) Objects.requireNonNull(map);
    }

    public MongoDB(final List<ServerAddress> addr, final List<MongoCredential> auth, final MongoClientOptions opts,
            final boolean repl, final String usedb, final boolean useid, final Map<String, IMongoDBOptions> dbops) {
        m_useid = useid;

        m_dbops = Objects.requireNonNull(dbops);

        m_usedb = StringOps.requireTrimOrNull(usedb);

        BSON.addEncodingHook(BigDecimal.class, new Transformer() {
            @Override
            public Object transform(final Object object) {
                if (null == object) {
                    return null;
                }
                return JSONUtils.asDouble(object);
            }
        });
        BSON.addEncodingHook(BigInteger.class, new Transformer() {
            @Override
            public Object transform(Object object) {
                if (null == object) {
                    return null;
                }
                Long lval = JSONUtils.asLong(object);

                if (null != lval) {
                    return lval;
                }
                return JSONUtils.asInteger(object);
            }
        });
        if (addr.isEmpty()) {
            throw new IllegalArgumentException("no ServerAddress");
        }
        if ((addr.size() == 1) && (false == repl)) {
            final ServerAddress main = addr.get(0);

            if (null == main) {
                throw new IllegalArgumentException("null ServerAddress");
            }
            if ((null == auth) || (auth.isEmpty())) {
                m_mongo = new MongoClient(main, Objects.requireNonNull(opts));
            } else {
                m_mongo = new MongoClient(main, auth, Objects.requireNonNull(opts));
            }
        } else {
            if ((null == auth) || (auth.isEmpty())) {
                m_mongo = new MongoClient(addr, Objects.requireNonNull(opts));
            } else {
                m_mongo = new MongoClient(addr, auth, Objects.requireNonNull(opts));
            }
        }
    }

    public boolean isAddingID() {
        return m_useid;
    }

    public void close() {
        if (null != m_mongo) {
            m_mongo.close();
        }
    }

    public List<String> getDatabaseNames() {
        return m_mongo.listDatabaseNames().into(new ArrayList<String>());
    }

    public final MDatabase db(final String name) throws Exception {
        return db(StringOps.requireTrimOrNull(name), isAddingID());
    }

    public final MDatabase db() throws Exception {
        return db(m_usedb, isAddingID());
    }

    public final MDatabase db(String name, boolean id) throws Exception {
        name = StringOps.requireTrimOrNull(name);

        IMongoDBOptions op = m_dbops.get(name);

        if (null != op) {
            id = op.isCreateID();
        }
        return new MDatabase(m_mongo.getDatabase(name), id, op);
    }

    public static final class MDatabase {
        private final MongoDatabase m_db;

        private final IMongoDBOptions m_op;

        private final boolean m_id;

        protected MDatabase(final MongoDatabase db, final boolean id, final IMongoDBOptions op) throws Exception {
            m_id = id;

            m_op = op;

            m_db = Objects.requireNonNull(db);
        }

        public boolean isCreateID() {
            return m_id;
        }

        public final String getName() {
            return m_db.getName();
        }

        public final void drop() {
            m_db.drop();
        }

        public final boolean isCollection(final String name) {
            return getCollectionNames().contains(StringOps.requireTrimOrNull(name));
        }

        public final List<String> getCollectionNames() {
            return m_db.listCollectionNames().into(new ArrayList<String>());
        }

        public final MCollection collection(String name) throws Exception {
            name = StringOps.requireTrimOrNull(name);

            if (null != m_op) {
                final IMongoDBCollectionOptions cops = m_op.getCollectionOptions(name);

                if (null != cops) {
                    return new MCollection(m_db.getCollection(name), cops.isCreateID());
                }
            }
            return new MCollection(m_db.getCollection(name), isCreateID());
        }

        public final MCollection collection(String name, final MCollectionPreferences opts) throws Exception {
            name = StringOps.requireTrimOrNull(name);

            boolean crid = isCreateID();

            if (null != m_op) {
                final IMongoDBCollectionOptions cops = m_op.getCollectionOptions(name);

                if (null != cops) {
                    crid = cops.isCreateID();
                }
                if ((null != opts) && (opts.isValid())) {
                    return opts.withCollectionOptions(m_db.getCollection(name), crid);
                }
            }
            return new MCollection(m_db.getCollection(name), crid);
        }
    }

    public static final class MCollectionPreferences {
        private final WriteConcern m_write;

        private final ReadPreference m_prefs;

        private final CodecRegistry m_codec;

        public MCollectionPreferences(final WriteConcern write, final ReadPreference prefs,
                final CodecRegistry codec) {
            m_write = write;

            m_prefs = prefs;

            m_codec = codec;
        }

        public MCollectionPreferences(final WriteConcern write) {
            this(write, null, null);
        }

        public MCollectionPreferences(final ReadPreference prefs) {
            this(null, prefs, null);
        }

        public MCollectionPreferences(final CodecRegistry codec) {
            this(null, null, codec);
        }

        public MCollectionPreferences(final WriteConcern write, final ReadPreference prefs) {
            this(write, prefs, null);
        }

        public MCollectionPreferences(final WriteConcern write, final CodecRegistry codec) {
            this(write, null, codec);
        }

        public MCollectionPreferences(final ReadPreference prefs, final CodecRegistry codec) {
            this(null, prefs, codec);
        }

        final boolean isValid() {
            return (false == ((null == m_write) && (null == m_prefs) && (null == m_codec)));
        }

        final MCollection withCollectionOptions(final MongoCollection<Document> collection, boolean id) {
            return new MCollection(
                    withCodecRegistry(withReadPreference(withWriteConcern(collection, m_write), m_prefs), m_codec),
                    id);
        }

        private final static MongoCollection<Document> withWriteConcern(final MongoCollection<Document> collection,
                final WriteConcern write) {
            if (null == write) {
                return collection;
            }
            return collection.withWriteConcern(write);
        }

        private final static MongoCollection<Document> withReadPreference(
                final MongoCollection<Document> collection, final ReadPreference prefs) {
            if (null == prefs) {
                return collection;
            }
            return collection.withReadPreference(prefs);
        }

        private final static MongoCollection<Document> withCodecRegistry(final MongoCollection<Document> collection,
                final CodecRegistry codec) {
            if (null == codec) {
                return collection;
            }
            return collection.withCodecRegistry(codec);
        }
    }

    public static final class MCollection {
        private final MongoCollection<Document> m_collection;

        private final boolean m_id;

        protected MCollection(final MongoCollection<Document> collection, final boolean id) {
            m_collection = Objects.requireNonNull(collection);

            m_id = id;
        }

        public boolean isCreateID() {
            return m_id;
        }

        public final String getName() {
            return m_collection.getNamespace().getCollectionName();
        }

        public final String createIndex(final Map<String, ?> keys) {
            return m_collection.createIndex(new Document(CAST_MAP(keys)));
        }

        public final String createIndex(final Map<String, ?> keys, final String name) {
            return m_collection.createIndex(new Document(CAST_MAP(keys)),
                    new IndexOptions().name(Objects.requireNonNull(name)));
        }

        public final String createIndex(final Map<String, ?> keys, final IndexOptions opts) {
            return m_collection.createIndex(new Document(CAST_MAP(keys)), Objects.requireNonNull(opts));
        }

        public final MCollection dropIndex(final String name) {
            m_collection.dropIndex(Objects.requireNonNull(name));

            return this;
        }

        public final MCollection dropIndexes() {
            m_collection.dropIndexes();

            return this;
        }

        public final MIndexCursor getIndexes() {
            return new MIndexCursor(m_collection.listIndexes());
        }

        @SafeVarargs
        public final <T extends Document> MAggregateCursor aggregate(final T... list) {
            return aggregate(new MAggregationPipeline(Objects.requireNonNull(list)));
        }

        public final <T extends Document> MAggregateCursor aggregate(final List<T> list) {
            return aggregate(new MAggregationPipeline(Objects.requireNonNull(list)));
        }

        public final MAggregateCursor aggregate(final MAggregationPipeline pipeline) {
            return new MAggregateCursor(m_collection.aggregate(Objects.requireNonNull(pipeline.list())));
        }

        public final void drop() {
            m_collection.drop();
        }

        public final MCollection deleteMany(final Map<String, ?> query) {
            return deleteMany(new MQuery(Objects.requireNonNull(query)));
        }

        public final MCollection deleteMany(final MQuery query) {
            m_collection.deleteMany(Objects.requireNonNull(query));

            return this;
        }

        public final MCollection deleteOne(final Map<String, ?> query) {
            return deleteOne(new MQuery(Objects.requireNonNull(query)));
        }

        public final MCollection deleteOne(final MQuery query) {
            m_collection.deleteOne(Objects.requireNonNull(query));

            return this;
        }

        @SuppressWarnings("unchecked")
        public final Map<String, ?> ensureHasID(final Map<String, ?> update) {
            Objects.requireNonNull(update);

            final Object id = update.get("id");

            if ((false == (id instanceof String)) || (null == StringOps.toTrimOrNull(id.toString()))) {
                ((Map<String, Object>) update).put("id", (new ObjectId()).toString());
            }
            return update;
        }

        public final Map<String, ?> insertOne(final Map<String, ?> record) {
            if (isCreateID()) {
                final Map<String, ?> withid = ensureHasID(Objects.requireNonNull(record));

                m_collection.insertOne(new Document(CAST_MAP(withid)));

                return withid;
            } else {
                m_collection.insertOne(new Document(CAST_MAP(record)));

                return record;
            }
        }

        public final MCollection insertMany(final List<Map<String, ?>> list) {
            Objects.requireNonNull(list);

            if (list.isEmpty()) {
                logger.warn("MCollection.insertMany(empty)");

                return this;
            }
            if (1 == list.size()) {
                insertOne(Objects.requireNonNull(list.get(0)));// let this do checkID

                return this;
            }
            final ArrayList<Document> save = new ArrayList<Document>(list.size());

            if (isCreateID()) {
                for (Map<String, ?> lmap : list) {
                    save.add(new Document(CAST_MAP(ensureHasID(lmap))));
                }
            } else {
                for (Map<String, ?> lmap : list) {
                    save.add(new Document(CAST_MAP(ensureHasID(lmap))));
                }
            }
            m_collection.insertMany(save);

            return this;
        }

        public final long count() {
            return m_collection.count();
        }

        public final long count(final Map<String, ?> query) {
            return count(new MQuery(Objects.requireNonNull(query)));
        }

        public final long count(final MQuery query) {
            return m_collection.count(Objects.requireNonNull(query));
        }

        public final MCursor find() throws Exception {
            return find(false);
        }

        final String getNameSpace() {
            return m_collection.getNamespace().toString();
        }

        public final MCursor find(final boolean with_id) throws Exception {
            if (with_id) {
                return new MCursor(m_collection.find());
            } else {
                return new MCursor(m_collection.find().projection(MProjection.NO_ID()));
            }
        }

        public final MCursor find(final Map<String, ?> query) throws Exception {
            return find(new MQuery(Objects.requireNonNull(query)), false);
        }

        public final MCursor find(final MQuery query) throws Exception {
            return find(Objects.requireNonNull(query), false);
        }

        public final MCursor find(final Map<String, ?> query, final boolean with_id) throws Exception {
            return find(new MQuery(Objects.requireNonNull(query)), with_id);
        }

        public final MCursor find(final MQuery query, final boolean with_id) throws Exception {
            if (with_id) {
                return new MCursor(m_collection.find(Objects.requireNonNull(query)));
            } else {
                return new MCursor(
                        m_collection.find(Objects.requireNonNull(query)).projection(MProjection.NO_ID()));
            }
        }

        public final MCursor find(final Map<String, ?> query, final Map<String, ?> fields) throws Exception {
            return find(new MQuery(Objects.requireNonNull(query)), new MProjection(Objects.requireNonNull(fields)));
        }

        public final MCursor find(final MQuery query, final Map<String, ?> fields) throws Exception {
            return find(Objects.requireNonNull(query), new MProjection(Objects.requireNonNull(fields)));
        }

        public final MCursor find(final Map<String, ?> query, final MProjection fields) throws Exception {
            return find(new MQuery(Objects.requireNonNull(query)), Objects.requireNonNull(fields), false);
        }

        public final MCursor find(final MQuery query, final MProjection fields) throws Exception {
            return find(Objects.requireNonNull(query), Objects.requireNonNull(fields), false);
        }

        public final MCursor find(final Map<String, ?> query, final Map<String, ?> fields, final boolean with_id)
                throws Exception {
            return find(new MQuery(Objects.requireNonNull(query)), new MProjection(Objects.requireNonNull(fields)),
                    with_id);
        }

        public final MCursor find(final Map<String, ?> query, final MProjection fields, final boolean with_id)
                throws Exception {
            return find(new MQuery(Objects.requireNonNull(query)), Objects.requireNonNull(fields), with_id);
        }

        public final MCursor find(final MQuery query, final MProjection fields, final boolean with_id)
                throws Exception {
            if (with_id) {
                return new MCursor(m_collection.find(Objects.requireNonNull(query))
                        .projection(Objects.requireNonNull(fields)));
            } else {
                return new MCursor(m_collection.find(Objects.requireNonNull(query))
                        .projection(MProjection.FIELDS(Objects.requireNonNull(fields), MProjection.NO_ID())));
            }
        }

        public final Map<String, ?> findAndModify(final Map<String, ?> query, final Map<String, ?> update) {
            return update(new MQuery(Objects.requireNonNull(query)), Objects.requireNonNull(update), false, true);
        }

        public final Map<String, ?> findAndModify(final MQuery query, final Map<String, ?> update) {
            return update(Objects.requireNonNull(query), Objects.requireNonNull(update), false, true);
        }

        public final Map<String, ?> upsert(final Map<String, ?> query, final Map<String, ?> update) {
            return upsert(new MQuery(Objects.requireNonNull(query)), Objects.requireNonNull(update));
        }

        public final Map<String, ?> upsert(final MQuery query, final Map<String, ?> update) {
            return update(Objects.requireNonNull(query), Objects.requireNonNull(update), true, true);
        }

        public final Map<String, ?> create(final Map<String, ?> record) {
            return insertOne(Objects.requireNonNull(record));
        }

        public final Map<String, ?> update(final Map<String, ?> query, final Map<String, ?> update,
                final boolean upsert, final boolean multi) {
            return update(new MQuery(Objects.requireNonNull(query)), Objects.requireNonNull(update), upsert, multi);
        }

        public final Map<String, ?> update(final MQuery query, final Map<String, ?> update, final boolean upsert,
                final boolean multi) {
            if (multi) {
                m_collection.updateMany(Objects.requireNonNull(query), new Document(CAST_MAP(update)),
                        new UpdateOptions().upsert(upsert));
            } else {
                m_collection.updateOne(Objects.requireNonNull(query), new Document(CAST_MAP(update)),
                        new UpdateOptions().upsert(upsert));
            }
            return update;
        }

        public final Map<String, ?> findOne(final Map<String, ?> query) {
            return findOne(new MQuery(Objects.requireNonNull(query)));
        }

        public final Map<String, ?> findOne(final MQuery query) {
            FindIterable<Document> iter = m_collection.find(Objects.requireNonNull(query)).limit(1)
                    .projection(MProjection.NO_ID());

            if (null != iter) {
                return iter.first();
            }
            return null;
        }

        public final boolean updateOne(final Map<String, ?> query, final Map<String, ?> update) {
            return updateOne(new MQuery(Objects.requireNonNull(query)), Objects.requireNonNull(update));
        }

        public final boolean updateOne(final MQuery query, final Map<String, ?> update) {
            return (m_collection.updateOne(Objects.requireNonNull(query), new Document(CAST_MAP(update)))
                    .getModifiedCount() == 1L);
        }

        public final long updateMany(final Map<String, ?> query, final Map<String, ?> update) {
            return updateMany(new MQuery(Objects.requireNonNull(query)), Objects.requireNonNull(update));
        }

        public final long updateMany(final MQuery query, final Map<String, ?> update) {
            return m_collection.updateMany(Objects.requireNonNull(query), new Document(CAST_MAP(update)),
                    new UpdateOptions().upsert(false)).getModifiedCount();
        }

        public final List<?> distinct(final String field) {
            return m_collection.distinct(StringOps.requireTrimOrNull(field), Document.class)
                    .into(new ArrayList<Document>());
        }

        public final List<?> distinct(final String field, final Map<String, ?> query) {
            return m_collection.distinct(StringOps.requireTrimOrNull(field), Document.class)
                    .filter(new Document(CAST_MAP(query))).into(new ArrayList<Document>());
        }
    }

    @SuppressWarnings("serial")
    private static class MAggregationOp extends Document {
        private MAggregationOp(final Document doc) {
            super(Objects.requireNonNull(doc));
        }

        protected static final MAggregationOp makeAggregationOp(final String op, final Map<String, ?> map) {
            final LinkedHashMap<String, Object> make = new LinkedHashMap<String, Object>(1);

            make.put(Objects.requireNonNull(op), Objects.requireNonNull(map));

            return new MAggregationOp(new Document(make));
        }

        protected static final MAggregationOp makeAggregationOp(final String op, final Document doc) {
            final LinkedHashMap<String, Object> make = new LinkedHashMap<String, Object>(1);

            make.put(Objects.requireNonNull(op), Objects.requireNonNull(doc));

            return new MAggregationOp(new Document(make));
        }

        public static final MAggregationMatch MATCH(final Map<String, ?> map) {
            return new MAggregationMatch(Objects.requireNonNull(map));
        }

        public static final MAggregationMatch MATCH(final Document doc) {
            return new MAggregationMatch(Objects.requireNonNull(doc));
        }

        public static final MAggregationGroup GROUP(final Map<String, ?> map) {
            return new MAggregationGroup(Objects.requireNonNull(map));
        }

        public static final MAggregationGroup GROUP(final Document doc) {
            return new MAggregationGroup(Objects.requireNonNull(doc));
        }
    }

    public static final class MAggregationGroup extends MAggregationOp {
        private static final long serialVersionUID = 3079372174680166319L;

        public MAggregationGroup(final Map<String, ?> map) {
            super(makeAggregationOp("$group", Objects.requireNonNull(map)));
        }

        public MAggregationGroup(final Document doc) {
            super(makeAggregationOp("$group", Objects.requireNonNull(doc)));
        }
    }

    public static final class MAggregationMatch extends MAggregationOp {
        private static final long serialVersionUID = -1138722876045817851L;

        public MAggregationMatch(final Map<String, ?> map) {
            super(makeAggregationOp("$match", Objects.requireNonNull(map)));
        }

        public MAggregationMatch(final Document doc) {
            super(makeAggregationOp("$match", Objects.requireNonNull(doc)));
        }
    }

    public static final class MAggregationPipeline {
        private final ArrayList<Document> m_pipeline = new ArrayList<Document>();

        public <T extends Document> MAggregationPipeline(final List<T> list) {
            m_pipeline.addAll(Objects.requireNonNull(list));
        }

        @SafeVarargs
        public <T extends Document> MAggregationPipeline(final T... list) {
            m_pipeline.addAll(Arrays.asList(Objects.requireNonNull(list)));
        }

        List<Document> list() {
            return m_pipeline;
        }
    }

    public static interface IMCursor extends Iterable<Map<String, ?>>, Iterator<Map<String, ?>>, Closeable {
        public <A extends Collection<? super Map<String, ?>>> A into(A target);
    }

    protected static abstract class AbstractMCursor<T extends MongoIterable<Document>> implements IMCursor {
        private final T m_iterab;

        private final MongoCursor<Document> m_cursor;

        private boolean m_closed = false;

        private boolean m_autoclose = true;

        protected AbstractMCursor(final T iter) {
            m_iterab = Objects.requireNonNull(iter);

            m_cursor = Objects.requireNonNull(m_iterab.iterator());
        }

        protected final T self() {
            return m_iterab;
        }

        @Override
        public <A extends Collection<? super Map<String, ?>>> A into(A target) {
            final A result = m_iterab.into(target);

            try {
                close();
            } catch (IOException e) {
                logger.error("Error in AbstractMCursor.into() ", e);
            }
            return result;
        }

        @Override
        public Iterator<Map<String, ?>> iterator() {
            return this;
        }

        @Override
        public boolean hasNext() {
            final boolean next = ((m_closed == false) && (m_cursor.hasNext()));

            if ((false == next) && (false == m_closed) && (m_autoclose)) {
                try {
                    close();
                } catch (Exception e) {
                    logger.error("Error in AbstractMCursor.close() ", e);
                }
            }
            return next;
        }

        public void setAutoClose(final boolean autoclose) {
            m_autoclose = autoclose;
        }

        @Override
        public Map<String, ?> next() {
            return m_cursor.next();
        }

        @Override
        public void remove() {
            m_cursor.remove();
        }

        @Override
        public void close() throws IOException {
            if (false == m_closed) {
                m_cursor.close();

                m_closed = true;
            }
        }
    }

    public static final class MIndexCursor extends AbstractMCursor<ListIndexesIterable<Document>> {
        protected MIndexCursor(final ListIndexesIterable<Document> index) {
            super(index);
        }
    }

    public static final class MAggregateCursor extends AbstractMCursor<AggregateIterable<Document>> {
        protected MAggregateCursor(final AggregateIterable<Document> aggreg) {
            super(aggreg);
        }
    }

    public static final class MCursor extends AbstractMCursor<FindIterable<Document>> {
        protected MCursor(final FindIterable<Document> finder) {
            super(finder);
        }

        public MCursor projection(final MProjection projection) {
            return new MCursor(self().projection(Objects.requireNonNull(projection)));
        }

        public MCursor skip(final int skip) {
            return new MCursor(self().skip(Math.max(0, skip)));
        }

        public MCursor limit(final int limit) {
            return new MCursor(self().limit(Math.max(0, limit)));
        }

        public MCursor sort(final Map<String, ?> sort) {
            return sort(new MSort(Objects.requireNonNull(sort)));
        }

        public MCursor sort(final MSort sort) {
            return new MCursor(self().sort(Objects.requireNonNull(sort)));
        }
    }

    @SuppressWarnings("serial")
    public static final class MSort extends Document {
        private static final BsonInt32 ORDER_A = new BsonInt32(0 + 1);

        private static final BsonInt32 ORDER_D = new BsonInt32(0 - 1);

        private MSort() {
        }

        @SuppressWarnings("unchecked")
        public MSort(final Map<String, ?> map) {
            super((Map<String, Object>) map);
        }

        public static final MSort ASCENDING(final String... fields) {
            return ASCENDING(Arrays.asList(fields));
        }

        public static final MSort ASCENDING(final List<String> fields) {
            return ORDER_BY(Objects.requireNonNull(fields), ORDER_A);
        }

        public static final MSort DESCENDING(final String... fields) {
            return DESCENDING(Arrays.asList(fields));
        }

        public static final MSort DESCENDING(final List<String> fields) {
            return ORDER_BY(Objects.requireNonNull(fields), ORDER_D);
        }

        public static final MSort ORDER_BY(final MSort... sorts) {
            return ORDER_BY(Arrays.asList(sorts));
        }

        public static final MSort ORDER_BY(final List<MSort> sorts) {
            Objects.requireNonNull(sorts);

            final MSort sort = new MSort();

            for (MSort s : sorts) {
                if (null != s) {
                    for (String k : s.keySet()) {
                        if (null != StringOps.toTrimOrNull(k)) {
                            sort.remove(k);

                            sort.append(k, s.get(k));
                        }
                    }
                } else {
                    logger.warn("MSort.ORDER_BY(null)");
                }
            }
            return sort;
        }

        private static final MSort ORDER_BY(final List<String> fields, final BsonInt32 value) {
            Objects.requireNonNull(fields);

            final MSort sort = new MSort();

            for (String name : fields) {
                if (null != StringOps.toTrimOrNull(name)) {
                    sort.remove(name);

                    sort.append(name, value);
                }
            }
            return sort;
        }
    }

    @SuppressWarnings("serial")
    public static final class MProjection extends Document {
        private static final BsonInt32 INCLUDE_N = new BsonInt32(0);

        private static final BsonInt32 INCLUDE_Y = new BsonInt32(1);

        private MProjection() {
        }

        @SuppressWarnings("unchecked")
        public MProjection(final Map<String, ?> map) {
            super(Objects.requireNonNull((Map<String, Object>) map));
        }

        private MProjection(final String name, final BsonValue value) {
            super(StringOps.requireTrimOrNull(name), value);
        }

        public static final MProjection INCLUDE(final String... fields) {
            return INCLUDE(Arrays.asList(fields));
        }

        public static final MProjection INCLUDE(final List<String> fields) {
            return COMBINE(Objects.requireNonNull(fields), INCLUDE_Y);
        }

        public static final MProjection EXCLUDE(final String... fields) {
            return EXCLUDE(Arrays.asList(fields));
        }

        public static final MProjection EXCLUDE(final List<String> fields) {
            return COMBINE(Objects.requireNonNull(fields), INCLUDE_N);
        }

        public static final MProjection NO_ID() {
            return new MProjection("_id", INCLUDE_N);
        }

        public static final MProjection FIELDS(final MProjection... projections) {
            return FIELDS(Arrays.asList(projections));
        }

        public static final MProjection FIELDS(final List<MProjection> projections) {
            final MProjection projection = new MProjection();

            for (MProjection p : projections) {
                for (String k : p.keySet()) {
                    if (null != (k = StringOps.toTrimOrNull(k))) {
                        projection.remove(k);

                        projection.append(k, p.get(k));
                    }
                }
            }
            return projection;
        }

        private static final MProjection COMBINE(final List<String> fields, final BsonInt32 value) {
            final MProjection projection = new MProjection();

            for (String name : fields) {
                if (null != (name = StringOps.toTrimOrNull(name))) {
                    projection.remove(name);

                    projection.append(name, value);
                }
            }
            return projection;
        }
    }

    @SuppressWarnings("serial")
    public static class MQuery extends Document {
        private MQuery() {
        }

        @SuppressWarnings("unchecked")
        public MQuery(final Map<String, ?> map) {
            super(Objects.requireNonNull((Map<String, Object>) map));
        }

        public static final MQuery QUERY(final Map<String, ?> map) {
            return new MQuery(map);
        }

        public static final <T> MQuery EQ(final String name, final T value) {
            return convert(eq(StringOps.requireTrimOrNull(name), value));
        }

        public static final <T> MQuery NE(final String name, final T value) {
            return convert(ne(StringOps.requireTrimOrNull(name), value));
        }

        public static final <T> MQuery GT(final String name, final T value) {
            return convert(gt(StringOps.requireTrimOrNull(name), value));
        }

        public static final <T> MQuery LT(final String name, final T value) {
            return convert(lt(StringOps.requireTrimOrNull(name), value));
        }

        public static final <T> MQuery GTE(final String name, final T value) {
            return convert(gte(StringOps.requireTrimOrNull(name), value));
        }

        public static final <T> MQuery LTE(final String name, final T value) {
            return convert(lte(StringOps.requireTrimOrNull(name), value));
        }

        @SafeVarargs
        public static final <T> MQuery IN(final String name, final T... list) {
            return IN(StringOps.requireTrimOrNull(name), Arrays.asList(list));
        }

        public static final <T> MQuery IN(final String name, final List<T> list) {
            return convert(in(StringOps.requireTrimOrNull(name), Objects.requireNonNull(list)));
        }

        @SafeVarargs
        public static final <T> MQuery NIN(String name, T... list) {
            return NIN(StringOps.requireTrimOrNull(name), Arrays.asList(list));
        }

        public static final <T> MQuery NIN(final String name, final List<T> list) {
            return convert(nin(StringOps.requireTrimOrNull(name), Objects.requireNonNull(list)));
        }

        public static final MQuery AND(final MQuery... list) {
            return AND(Arrays.asList(list));
        }

        public static final MQuery AND(final List<MQuery> list) {
            return convert(and(new ArrayList<Bson>(Objects.requireNonNull(list))));
        }

        public static final MQuery OR(final MQuery... list) {
            return OR(Arrays.asList(list));
        }

        public static final MQuery OR(final List<MQuery> list) {
            return convert(or(new ArrayList<Bson>(Objects.requireNonNull(list))));
        }

        public static final MQuery NOT(final MQuery query) {
            return convert(not(Objects.requireNonNull(query)));
        }

        public static final MQuery EXISTS(final String name, final boolean exists) {
            return convert(exists(StringOps.requireTrimOrNull(name), exists));
        }

        public static final MQuery EXISTS(final String name) {
            return convert(exists(StringOps.requireTrimOrNull(name), true));
        }

        private static final MQuery convert(final Bson b) {
            return new MQuery() {
                @Override
                public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> documentClass,
                        final CodecRegistry codecRegistry) {
                    return b.toBsonDocument(documentClass, codecRegistry);
                }
            };
        }
    }
}