com.torodb.mongowp.mongoserver.api.toro.ToroQueryCommandProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.torodb.mongowp.mongoserver.api.toro.ToroQueryCommandProcessor.java

Source

/*
 *     This file is part of ToroDB.
 *
 *     ToroDB is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Affero General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     ToroDB is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Affero General Public License for more details.
 *
 *     You should have received a copy of the GNU Affero General Public License
 *     along with ToroDB. If not, see <http://www.gnu.org/licenses/>.
 *
 *     Copyright (c) 2014, 8Kdata Technology
 *     
 */

package com.torodb.mongowp.mongoserver.api.toro;

import com.eightkdata.mongowp.messages.request.RequestOpCode;
import com.eightkdata.mongowp.mongoserver.api.QueryCommandProcessor;
import com.eightkdata.mongowp.mongoserver.api.callback.LastError;
import com.eightkdata.mongowp.mongoserver.api.callback.MessageReplier;
import com.eightkdata.mongowp.mongoserver.api.commands.*;
import com.eightkdata.mongowp.mongoserver.protocol.MongoWP;
import com.eightkdata.mongowp.mongoserver.protocol.MongoWP.ErrorCode;
import com.eightkdata.nettybson.api.BSONDocument;
import com.eightkdata.nettybson.mongodriver.MongoBSONDocument;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.mongodb.WriteConcern;
import com.torodb.BuildProperties;
import com.torodb.kvdocument.conversion.mongo.MongoValueConverter;
import com.torodb.kvdocument.values.DocValue;
import com.torodb.kvdocument.values.ObjectValue;
import com.torodb.mongowp.mongoserver.api.toro.util.BSONToroDocument;
import com.torodb.torod.core.WriteFailMode;
import com.torodb.torod.core.annotations.DatabaseName;
import com.torodb.torod.core.connection.*;
import com.torodb.torod.core.cursors.UserCursor;
import com.torodb.torod.core.dbWrapper.exceptions.ImplementationDbException;
import com.torodb.torod.core.exceptions.ExistentIndexException;
import com.torodb.torod.core.exceptions.UserToroException;
import com.torodb.torod.core.language.AttributeReference;
import com.torodb.torod.core.language.operations.DeleteOperation;
import com.torodb.torod.core.language.operations.UpdateOperation;
import com.torodb.torod.core.language.querycriteria.QueryCriteria;
import com.torodb.torod.core.language.querycriteria.TrueQueryCriteria;
import com.torodb.torod.core.language.update.UpdateAction;
import com.torodb.torod.core.pojos.CollectionMetainfo;
import com.torodb.torod.core.pojos.Database;
import com.torodb.torod.core.pojos.IndexedAttributes;
import com.torodb.torod.core.pojos.NamedToroIndex;
import com.torodb.torod.core.subdocument.ToroDocument;
import com.torodb.torod.core.utils.DocValueQueryCriteriaEvaluator;
import com.torodb.translator.*;
import io.netty.util.AttributeMap;
import org.bson.BSONObject;
import org.bson.BasicBSONObject;

import javax.annotation.Nonnull;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.bson.types.BasicBSONList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 */
public class ToroQueryCommandProcessor implements QueryCommandProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ToroQueryCommandProcessor.class);
    private final QueryCriteriaTranslator queryCriteriaTranslator;
    private final BuildProperties buildProperties;
    private final String databaseName;

    @Inject
    public ToroQueryCommandProcessor(BuildProperties buildProperties,
            QueryCriteriaTranslator queryCriteriaTranslator, @DatabaseName String databaseName) {
        this.buildProperties = buildProperties;
        this.queryCriteriaTranslator = queryCriteriaTranslator;
        this.databaseName = databaseName;
    }

    @Override
    public CountReply count(CountRequest request) throws Exception {
        AttributeMap attributeMap = request.getAttributes();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        QueryCriteria queryCriteria;
        if (request.getQuery() == null) {
            queryCriteria = TrueQueryCriteria.getInstance();
        } else {
            queryCriteria = queryCriteriaTranslator.translate(request.getQuery());
        }

        ToroTransaction transaction = connection.createTransaction();

        try {
            Integer count = transaction.count(request.getCollection(), queryCriteria).get();

            return new CountReply(count);
        } finally {
            transaction.close();
        }
    }

    @Override
    public com.eightkdata.mongowp.mongoserver.api.pojos.InsertResponse insert(BSONDocument document,
            AttributeMap attributeMap) throws Exception {
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        String collection = (String) document.getValue("insert");
        WriteFailMode writeFailMode = getWriteFailMode(document);
        WriteConcern writeConcern = getWriteConcern(document);
        Iterable<?> documents = (Iterable<?>) document.getValue("documents");
        List<ToroDocument> inserts = new ArrayList<ToroDocument>();
        Iterator<?> documentsIterator = documents.iterator();
        while (documentsIterator.hasNext()) {
            BSONObject object = (BSONObject) documentsIterator.next();
            inserts.add(new BSONToroDocument(object));
        }

        ToroTransaction transaction = connection.createTransaction();

        ToroInsertResponse.Builder responseBuilder = new ToroInsertResponse.Builder();

        try {
            Future<InsertResponse> futureInsertResponse = transaction.insertDocuments(collection, inserts,
                    writeFailMode);

            Future<?> futureCommitResponse = transaction.commit();

            if (writeConcern.getW() > 0) {
                InsertResponse insertResponse = futureInsertResponse.get();
                futureCommitResponse.get();
                responseBuilder.setN(insertResponse.getInsertedSize());
            }
            LastError lastError = new ToroLastError(RequestOpCode.OP_QUERY,
                    QueryAndWriteOperationsQueryCommand.delete, futureInsertResponse, futureCommitResponse, false,
                    null);
            attributeMap.attr(ToroRequestProcessor.LAST_ERROR).set(lastError);

            responseBuilder.setOk(true);
            return responseBuilder.build();
        } finally {
            transaction.close();
        }
    }

    @Override
    public void update(BSONDocument document, MessageReplier messageReplier) throws Exception {
        AttributeMap attributeMap = messageReplier.getAttributeMap();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        Map<String, Object> keyValues = new HashMap<String, Object>();

        String collection = (String) document.getValue("update");

        WriteFailMode writeFailMode = getWriteFailMode(document);
        WriteConcern writeConcern = getWriteConcern(document);
        Iterable<?> documents = (Iterable<?>) document.getValue("updates");
        List<UpdateOperation> updates = new ArrayList<UpdateOperation>();
        Iterator<?> documentsIterator = documents.iterator();
        while (documentsIterator.hasNext()) {
            BSONObject object = (BSONObject) documentsIterator.next();
            QueryCriteria queryCriteria = queryCriteriaTranslator.translate((BSONObject) object.get("q"));
            UpdateAction updateAction = UpdateActionTranslator.translate((BSONObject) object.get("u"));
            boolean upsert = getBoolean(object, "upsert", false);
            boolean onlyOne = !getBoolean(object, "multi", false);
            updates.add(new UpdateOperation(queryCriteria, updateAction, upsert, onlyOne));
        }

        ToroTransaction transaction = connection.createTransaction();

        try {
            Future<UpdateResponse> futureUpdateResponse = transaction.update(collection, updates, writeFailMode);

            Future<?> futureCommitResponse = transaction.commit();

            if (writeConcern.getW() > 0) {
                UpdateResponse updateResponse = futureUpdateResponse.get();
                futureCommitResponse.get();
                keyValues.put("n", updateResponse.getModified());
            }
            LastError lastError = new ToroLastError(RequestOpCode.OP_QUERY,
                    QueryAndWriteOperationsQueryCommand.update, futureUpdateResponse, futureCommitResponse, false,
                    null);
            attributeMap.attr(ToroRequestProcessor.LAST_ERROR).set(lastError);
        } finally {
            transaction.close();
        }

        keyValues.put("ok", MongoWP.OK);
        BSONDocument reply = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(reply);
    }

    private boolean getBoolean(BSONDocument document, String key, boolean defaultValue) {
        if (!document.hasKey(key)) {
            return defaultValue;
        }

        Object value = document.getValue(key);

        if (value instanceof Boolean) {
            return ((Boolean) value);
        }

        if (value instanceof Number) {
            return ((Number) value).intValue() > 0;
        }

        throw new IllegalArgumentException("Value " + value + " for key " + key + " is not boolean");
    }

    private boolean getBoolean(BSONObject object, String key, boolean defaultValue) {
        if (!object.containsField(key)) {
            return defaultValue;
        }

        Object value = object.get(key);

        if (value instanceof Boolean) {
            return ((Boolean) value);
        }

        if (value instanceof Number) {
            return ((Number) value).intValue() > 0;
        }

        throw new IllegalArgumentException("Value " + value + " for key " + key + " is not boolean");
    }

    @Override
    public void delete(BSONDocument document, MessageReplier messageReplier) throws Exception {
        AttributeMap attributeMap = messageReplier.getAttributeMap();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        Map<String, Object> keyValues = new HashMap<String, Object>();

        String collection = (String) document.getValue("delete");

        WriteFailMode writeFailMode = getWriteFailMode(document);
        WriteConcern writeConcern = getWriteConcern(document);
        Iterable<?> documents = (Iterable<?>) document.getValue("deletes");
        List<DeleteOperation> deletes = new ArrayList<DeleteOperation>();
        Iterator<?> documentsIterator = documents.iterator();
        while (documentsIterator.hasNext()) {
            BSONObject object = (BSONObject) documentsIterator.next();
            QueryCriteria queryCriteria = queryCriteriaTranslator.translate((BSONObject) object.get("q"));
            boolean singleRemove = getBoolean(object, "limit", false);
            deletes.add(new DeleteOperation(queryCriteria, singleRemove));
        }

        ToroTransaction transaction = connection.createTransaction();

        try {
            Future<DeleteResponse> futureDeleteResponse = transaction.delete(collection, deletes, writeFailMode);

            Future<?> futureCommitResponse = transaction.commit();

            if (writeConcern.getW() > 0) {
                DeleteResponse deleteResponse = futureDeleteResponse.get();
                futureCommitResponse.get();
                keyValues.put("n", deleteResponse.getDeleted());
            }
            LastError lastError = new ToroLastError(RequestOpCode.OP_QUERY,
                    QueryAndWriteOperationsQueryCommand.delete, futureDeleteResponse, futureCommitResponse, false,
                    null);
            attributeMap.attr(ToroRequestProcessor.LAST_ERROR).set(lastError);
        } finally {
            transaction.close();
        }

        keyValues.put("ok", MongoWP.OK);
        BSONDocument reply = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(reply);
    }

    @Override
    public void drop(BSONDocument document, MessageReplier messageReplier) throws Exception {
        AttributeMap attributeMap = messageReplier.getAttributeMap();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        Map<String, Object> keyValues = new HashMap<String, Object>();

        String collection = (String) document.getValue("drop");

        connection.dropCollection(collection);

        keyValues.put("ok", MongoWP.OK);
        BSONDocument reply = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(reply);

    }

    private AttributeReference parseAttributeReference(String path) {
        AttributeReference.Builder builder = new AttributeReference.Builder();

        StringTokenizer st = new StringTokenizer(path, ".");
        while (st.hasMoreTokens()) {
            builder.addObjectKey(st.nextToken());
        }
        return builder.build();
    }

    //TODO: implement with toro natives
    @Override
    public void createIndexes(@Nonnull BSONDocument document, @Nonnull MessageReplier messageReplier)
            throws Exception {
        BSONObject keyValues = new BasicBSONObject();

        Object collectionValue = document.getValue("createIndexes");
        if (!(collectionValue instanceof String)) {
            keyValues.put("ok", MongoWP.KO);
            keyValues.put("code", (double) 13111);
            keyValues.put("errmsg", "exception: wrong type for field (createIndexes)");
            messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
            return;
        }
        String collection = (String) collectionValue;
        Object indexesValue = document.getValue("indexes");
        if (!(indexesValue instanceof List)) {
            keyValues.put("ok", MongoWP.KO);
            keyValues.put("errmsg", "indexes has to be an array");
            messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
            return;
        }
        //TODO: Unsafe cast
        List<BSONObject> uncastedIndexes = (List<BSONObject>) indexesValue;

        AttributeMap attributeMap = messageReplier.getAttributeMap();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        int numIndexesBefore;

        ToroTransaction transaction = null;
        try {
            transaction = connection.createTransaction();

            numIndexesBefore = transaction.getIndexes(collection).size();
            try {
                for (BSONObject uncastedIndex : uncastedIndexes) {
                    String name = (String) uncastedIndex.removeField("name");
                    BSONObject key = (BSONObject) uncastedIndex.removeField("key");
                    Boolean unique = (Boolean) uncastedIndex.removeField("unique");
                    unique = unique != null ? unique : false;
                    Boolean sparse = (Boolean) uncastedIndex.removeField("sparse");
                    sparse = sparse != null ? sparse : false;

                    if (!uncastedIndex.keySet().isEmpty()) {
                        keyValues.put("ok", MongoWP.KO);
                        String errmsg = "Options " + uncastedIndex.keySet().toString() + " are not supported";
                        keyValues.put("errmsg", errmsg);
                        messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
                        return;
                    }

                    IndexedAttributes.Builder indexedAttsBuilder = new IndexedAttributes.Builder();

                    for (String path : key.keySet()) {
                        AttributeReference attRef = parseAttributeReference(path);
                        //TODO: Check that key.get(path) is a number!!
                        boolean ascending = ((Number) key.get(path)).intValue() > 0;

                        indexedAttsBuilder.addAttribute(attRef, ascending);
                    }

                    transaction.createIndex(collection, name, indexedAttsBuilder.build(), unique, sparse).get();
                }

                int numIndexesAfter = transaction.getIndexes(collection).size();

                transaction.commit().get();

                keyValues.put("ok", MongoWP.OK);
                keyValues.put("createdCollectionAutomatically", false);
                keyValues.put("numIndexesBefore", numIndexesBefore);
                keyValues.put("numIndexesAfter", numIndexesAfter);

                BSONDocument reply = new MongoBSONDocument(keyValues);
                messageReplier.replyMessageNoCursor(reply);
            } catch (ExecutionException ex) {
                if (ex.getCause() instanceof ExistentIndexException) {
                    keyValues.put("ok", MongoWP.OK);
                    keyValues.put("note", ex.getCause().getMessage());
                    keyValues.put("numIndexesBefore", numIndexesBefore);

                    messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
                } else {
                    throw ex;
                }
            }
        } finally {
            if (transaction != null) {
                transaction.close();
            }
        }

    }

    @Override
    public void deleteIndexes(BSONDocument query, MessageReplier messageReplier) throws Exception {
        Map<String, Object> keyValues = Maps.newHashMap();

        Object dropIndexesValue = query.getValue("deleteIndexes");
        Object indexValue = query.getValue("index");

        if (!(dropIndexesValue instanceof String)) {
            keyValues.put("ok", MongoWP.KO);
            keyValues.put("errmsg", "The field 'dropIndexes' must be a string");
            messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
            return;
        }
        if (!(indexValue instanceof String)) {
            keyValues.put("ok", MongoWP.KO);
            keyValues.put("errmsg", "The field 'index' must be a string");
            messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
            return;
        }

        String collection = (String) dropIndexesValue;
        String indexName = (String) indexValue;

        AttributeMap attributeMap = messageReplier.getAttributeMap();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        ToroTransaction transaction = null;
        try {
            transaction = connection.createTransaction();

            if (indexName.equals("*")) { //TODO: Support * in deleteIndexes
                keyValues.put("ok", MongoWP.KO);
                keyValues.put("errmsg", "The wildcard '*' is not supported by ToroDB right now");
                messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
                return;
            }

            Boolean removed = transaction.dropIndex(collection, indexName).get();
            if (!removed) {
                keyValues.put("ok", MongoWP.KO);
                keyValues.put("errmsg", "index not found with name [" + indexName + "]");
                messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
                return;
            }

            transaction.commit();

            keyValues.put("ok", MongoWP.OK);
            messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
        } finally {
            if (transaction != null) {
                transaction.close();
            }
        }
    }

    @Override
    public void create(BSONDocument document, MessageReplier messageReplier) throws Exception {
        String collection = (String) document.getValue("create");
        Boolean capped = (Boolean) document.getValue("capped");

        if (capped != null && capped) { // Other flags silently ignored
            messageReplier.replyQueryCommandFailure(ErrorCode.UNIMPLEMENTED_FLAG, "capped");
            return;
        }

        AttributeMap attributeMap = messageReplier.getAttributeMap();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        Map<String, Object> keyValues = new HashMap<String, Object>();
        if (connection.createCollection(collection, null)) {
            keyValues.put("ok", MongoWP.OK);
        } else {
            keyValues.put("ok", MongoWP.KO);
            keyValues.put("errmsg", "collection already exists");
        }

        messageReplier.replyMessageNoCursor(new MongoBSONDocument(keyValues));
    }

    private WriteFailMode getWriteFailMode(BSONDocument document) {
        return WriteFailMode.TRANSACTIONAL;
    }

    private WriteConcern getWriteConcern(BSONDocument document) {
        WriteConcern writeConcern = WriteConcern.ACKNOWLEDGED;
        if (document.hasKey("writeConcern")) {
            BSONObject writeConcernObject = (BSONObject) document.getValue("writeConcern");
            Object w = writeConcernObject.get("w");
            int wtimeout = 0;
            boolean fsync = false;
            boolean j = false;
            boolean continueOnError = false;
            Object jObject = writeConcernObject.get("j");
            if (jObject != null && jObject instanceof Boolean && (Boolean) jObject) {
                fsync = true;
                j = true;
                continueOnError = true;
            }
            Object wtimeoutObject = writeConcernObject.get("wtimneout");
            if (wtimeoutObject != null && wtimeoutObject instanceof Number) {
                wtimeout = ((Number) wtimeoutObject).intValue();
            }
            if (w != null) {
                if (w instanceof Number) {
                    if (((Number) w).intValue() <= 1 && wtimeout > 0) {
                        throw new IllegalArgumentException("wtimeout cannot be grater than 0 for w <= 1");
                    }

                    writeConcern = new WriteConcern(((Number) w).intValue(), wtimeout, fsync, j);
                } else if (w instanceof String && w.equals("majority")) {
                    if (wtimeout > 0) {
                        throw new IllegalArgumentException("wtimeout cannot be grater than 0 for w <= 1");
                    }

                    writeConcern = new WriteConcern.Majority(wtimeout, fsync, j);
                } else {
                    throw new IllegalArgumentException("w:" + w + " is not supported");
                }
            }
        }
        return writeConcern;
    }

    @Override
    public void getLastError(Object w, boolean j, boolean fsync, int wtimeout, MessageReplier messageReplier)
            throws Exception {
        AttributeMap attributeMap = messageReplier.getAttributeMap();
        LastError lastError = attributeMap.attr(ToroRequestProcessor.LAST_ERROR).get();
        lastError.getLastError(w, j, fsync, wtimeout, messageReplier);
    }

    @Override
    public void validate(String database, BSONDocument document, MessageReplier messageReplier) {
        Map<String, Object> keyValues = new HashMap<String, Object>();

        String collection = (String) document.getValue("validate");
        boolean full = getBoolean(document, "full", false);
        String ns = database + "." + collection;

        keyValues.put("ns", ns);
        keyValues.put("firstExtent", "2:4b4b000 ns:" + ns);
        keyValues.put("lastExtent", "2:4b4b000 ns:" + ns);
        keyValues.put("extentCount", 1);
        keyValues.put("datasize", 0);
        keyValues.put("nrecords", 0);
        keyValues.put("lastExtentSize", 8192);
        keyValues.put("padding", 0);

        Map<String, Object> firstExtentDetailsKeyValues = new HashMap<String, Object>();
        firstExtentDetailsKeyValues.put("loc", "2:4b4b000");
        firstExtentDetailsKeyValues.put("xnext", null);
        firstExtentDetailsKeyValues.put("xprev", null);
        firstExtentDetailsKeyValues.put("nsdiag", ns);
        firstExtentDetailsKeyValues.put("size", 8192);
        firstExtentDetailsKeyValues.put("firstRecord", null);
        firstExtentDetailsKeyValues.put("firstRecord", null);
        keyValues.put("firstExtentDetails", new BasicBSONObject(firstExtentDetailsKeyValues));

        keyValues.put("deletedCount", 0);
        keyValues.put("deletedSize", 0);
        keyValues.put("nIndexes", 1);

        Map<String, Object> keysPerIndexKeyValues = new HashMap<String, Object>();
        keysPerIndexKeyValues.put(ns + ".$_id_", 0);
        keyValues.put("keysPerIndex", new BasicBSONObject(keysPerIndexKeyValues));

        keyValues.put("valid", true);
        keyValues.put("errors", new String[0]);
        if (!full) {
            keyValues.put("warning",
                    "Some checks omitted for speed. use {full:true} option to do more thorough scan.");
        }
        keyValues.put("ok", MongoWP.OK);

        BSONDocument reply = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(reply);
    }

    @Override
    public void whatsmyuri(String host, int port, MessageReplier messageReplier) {
        Map<String, Object> keyValues = new HashMap<String, Object>();

        keyValues.put("you", host + ":" + port);
        keyValues.put("ok", MongoWP.OK);

        BSONDocument document = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(document);
    }

    @Override
    public void replSetGetStatus(MessageReplier messageReplier) {
        Map<String, Object> keyValues = new HashMap<String, Object>();

        keyValues.put("errmsg", "not running with --replSet");
        keyValues.put("ok", MongoWP.KO);

        BSONDocument document = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(document);
    }

    @Override
    public void getLog(GetLogType log, MessageReplier messageReplier) {
        Map<String, Object> keyValues = new HashMap<String, Object>();

        if (log == GetLogType.startupWarnings) {
            keyValues.put("totalLinesWritten", 0);
            keyValues.put("log", new String[0]);
            keyValues.put("ok", MongoWP.OK);
        } else {
            keyValues.put("ok", MongoWP.KO);
        }

        BSONDocument document = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(document);
    }

    @Override
    public void isMaster(MessageReplier messageReplier) {
        Map<String, Object> keyValues = new HashMap<String, Object>();

        keyValues.put("ismaster", Boolean.TRUE);
        keyValues.put("maxBsonObjectSize", MongoWP.MAX_BSON_DOCUMENT_SIZE);
        keyValues.put("maxMessageSizeBytes", MongoWP.MAX_MESSAGE_SIZE_BYTES);
        keyValues.put("maxWriteBatchSize", MongoWP.MAX_WRITE_BATCH_SIZE);
        keyValues.put("localTime", System.currentTimeMillis());
        keyValues.put("maxWireVersion", MongoWP.MAX_WIRE_VERSION);
        keyValues.put("minWireVersion", MongoWP.MIN_WIRE_VERSION);
        keyValues.put("ok", MongoWP.OK);

        BSONDocument document = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(document);
    }

    @Override
    public void buildInfo(MessageReplier messageReplier) {
        Map<String, Object> keyValues = new HashMap<String, Object>();

        keyValues.put("version",
                MongoWP.VERSION_STRING + " (compatible; ToroDB " + buildProperties.getFullVersion() + ")");
        keyValues.put("gitVersion", buildProperties.getGitCommitId());
        keyValues.put("sysInfo", buildProperties.getOsName() + " " + buildProperties.getOsVersion() + " "
                + buildProperties.getOsArch());
        keyValues.put("versionArray", Lists.newArrayList(buildProperties.getMajorVersion(),
                buildProperties.getMinorVersion(), buildProperties.getSubVersion(), 0));
        keyValues.put("bits", "amd64".equals(buildProperties.getOsArch()) ? 64 : 32);
        keyValues.put("debug", false);
        keyValues.put("maxBsonObjectSize", MongoWP.MAX_BSON_DOCUMENT_SIZE);
        keyValues.put("ok", MongoWP.OK);

        BSONDocument document = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(document);
    }

    @Override
    public boolean handleError(QueryCommand userCommand, MessageReplier messageReplier, Throwable throwable)
            throws Exception {
        //TODO: Map real mongo error codes      
        ErrorCode errorCode = ErrorCode.INTERNAL_ERROR;
        AttributeMap attributeMap = messageReplier.getAttributeMap();
        LastError lastError = new ToroLastError(RequestOpCode.OP_QUERY, userCommand, null, null, true, errorCode);
        attributeMap.attr(ToroRequestProcessor.LAST_ERROR).set(lastError);
        messageReplier.replyQueryCommandFailure(errorCode, throwable.getMessage());

        return true;
    }

    @Override
    public void ping(MessageReplier messageReplier) {
        Map<String, Object> keyValues = new HashMap<String, Object>();
        keyValues.put("ok", MongoWP.OK);

        BSONDocument document = new MongoBSONDocument(keyValues);
        messageReplier.replyMessageNoCursor(document);
    }

    @Override
    public void listDatabases(MessageReplier messageReplier)
            throws ExecutionException, InterruptedException, ImplementationDbException {
        AttributeMap attributeMap = messageReplier.getAttributeMap();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        ToroTransaction transaction = connection.createTransaction();

        try {
            List<? extends Database> databases = transaction.getDatabases().get();

            double totalSize = 0;
            List<BSONObject> databaseDocs = Lists.newArrayListWithCapacity(databases.size());
            for (Database database : databases) {
                BSONObject databaseDoc = new BasicBSONObject();
                databaseDoc.put("name", database.getName());
                databaseDoc.put("sizeOnDisk", Long.valueOf(database.getSize()).doubleValue());
                //TODO: This is not true, but...
                databaseDoc.put("empty", database.getSize() == 0);

                totalSize += database.getSize();
                databaseDocs.add(databaseDoc);
            }
            BSONObject response = new BasicBSONObject(3);

            response.put("databases", databaseDocs);
            response.put("totalSize", totalSize);
            response.put("ok", MongoWP.OK);

            BSONDocument document = new MongoBSONDocument(response.toMap());
            messageReplier.replyMessageNoCursor(document);
        } finally {
            transaction.close();
        }
    }

    @Override
    public CollStatsReply collStats(CollStatsRequest request) throws Exception {
        CollStatsReply.Builder replyBuilder = new CollStatsReply.Builder(request.getDatabase(),
                request.getCollection());
        replyBuilder.setCapped(false).setLastExtentSize(1).setNumExtents(1).setPaddingFactor(0)
                .setScale(request.getScale().intValue()).setUsePowerOf2Sizes(false);

        AttributeMap attributeMap = request.getAttributes();
        ToroConnection connection = attributeMap.attr(ToroRequestProcessor.CONNECTION).get();

        ToroTransaction transaction = connection.createTransaction();
        int scale = replyBuilder.getScale();
        try {
            Collection<? extends NamedToroIndex> indexes = transaction.getIndexes(request.getCollection());
            Map<String, Long> sizeByMap = Maps.newHashMapWithExpectedSize(indexes.size());
            for (NamedToroIndex index : indexes) {
                Long indexSize = transaction.getIndexSize(request.getCollection(), index.getName()).get();
                sizeByMap.put(index.getName(), indexSize / scale);
            }

            replyBuilder.setSizeByIndex(sizeByMap);

            replyBuilder.setIdIndexExists(sizeByMap.containsKey("_id"));
            replyBuilder
                    .setCount(transaction.count(request.getCollection(), TrueQueryCriteria.getInstance()).get());
            replyBuilder.setSize(transaction.getDocumentsSize(request.getCollection()).get() / scale);
            replyBuilder.setStorageSize(transaction.getCollectionSize(request.getCollection()).get() / scale);

            return replyBuilder.build();
        } finally {
            transaction.close();
        }
    }

    @Override
    public void unimplemented(QueryCommand userCommand, MessageReplier messageReplier) throws Exception {
        //TODO: Map real mongo error codes      
        ErrorCode errorCode = ErrorCode.UNIMPLEMENTED_COMMAND;
        AttributeMap attributeMap = messageReplier.getAttributeMap();
        LastError lastError = new ToroLastError(RequestOpCode.OP_QUERY, userCommand, null, null, true, errorCode);
        attributeMap.attr(ToroRequestProcessor.LAST_ERROR).set(lastError);
        messageReplier.replyQueryCommandFailure(errorCode, userCommand.getKey());
    }

    @Override
    public void getnonce(MessageReplier messageReplier) {
        LOGGER.warn("Authentication not supported. Operation 'getnonce' " + "called. A fake value is returned");
        BSONObject replyObj = new BasicBSONObject();
        Random r = new Random();
        String nonce = Long.toHexString(r.nextLong());
        replyObj.put("nonce", nonce);
        replyObj.put("ok", MongoWP.OK);
        messageReplier.replyMessageNoCursor(new MongoBSONDocument(replyObj));
    }

    @Override
    public void listCollections(MessageReplier messageReplier, BSONDocument query) throws Exception {
        ToroConnection connection = messageReplier.getAttributeMap().attr(ToroRequestProcessor.CONNECTION).get();

        assert query.getValue("listCollections") != null;
        Object filterObject = query.getValue("filter");
        QueryCriteria filter;
        if (filterObject == null) {
            filter = TrueQueryCriteria.getInstance();
        } else {
            if (filterObject instanceof BSONObject) {
                filter = queryCriteriaTranslator.translate((BSONObject) filterObject);
            } else {
                throw new UserToroException(
                        "Filter " + filterObject + " has not been recognized as a valid filter");
            }
        }
        Predicate<DocValue> predicate = DocValueQueryCriteriaEvaluator.createPredicate(filter);

        UserCursor<CollectionMetainfo> cursor = connection.openCollectionsMetainfoCursor();

        ArrayList<ObjectValue> collectionsInfo = Lists.newArrayList(Iterables
                .filter(Iterables.transform(cursor.readAll(), new CollectionMetainfoToDocValue()), predicate));
        BasicBSONList firstBatch = new BasicBSONList();
        for (ObjectValue colInfo : collectionsInfo) {
            firstBatch.add(MongoValueConverter.translateObject(colInfo));
        }

        BasicBSONObject root = new BasicBSONObject();
        root.append("ok", MongoWP.OK);
        root.append("cursor", new BasicBSONObject().append("id", (long) 0)
                .append("ns", databaseName + ".$cmd.listCollections").append("firstBatch", firstBatch));
        BSONDocument result = new MongoBSONDocument((BSONObject) root);

        messageReplier.replyMessageNoCursor(result);
    }

    @Override
    public void listIndexes(MessageReplier messageReplier, String collection) throws Exception {
        ToroConnection connection = messageReplier.getAttributeMap().attr(ToroRequestProcessor.CONNECTION).get();
        ToroTransaction transaction = connection.createTransaction();

        Collection<? extends NamedToroIndex> indexes = transaction.getIndexes(collection);

        BasicBSONList firstBatch = new BasicBSONList();

        for (NamedToroIndex index : indexes) {
            String collectionNamespace = databaseName + '.' + collection;
            ObjectValue.Builder objBuider = new ObjectValue.Builder().putValue("v", 1)
                    .putValue("name", index.getName()).putValue("ns", collectionNamespace)
                    .putValue("key", new ObjectValue.Builder());
            ObjectValue.Builder keyBuilder = new ObjectValue.Builder();
            for (Map.Entry<AttributeReference, Boolean> entrySet : index.getAttributes().entrySet()) {
                keyBuilder.putValue(entrySet.getKey().toString(), entrySet.getValue() ? 1 : -1);
            }
            objBuider.putValue("key", keyBuilder);

            firstBatch.add(MongoValueConverter.translateObject(objBuider.build()));
        }

        BasicBSONObject root = new BasicBSONObject();
        root.append("ok", MongoWP.OK);
        root.append("cursor", new BasicBSONObject().append("id", (long) 0)
                .append("ns", databaseName + ".$cmd.listIndexes." + collection).append("firstBatch", firstBatch));
        BSONDocument result = new MongoBSONDocument((BSONObject) root);

        messageReplier.replyMessageNoCursor(result);
    }

    private static class CollectionMetainfoToDocValue implements Function<CollectionMetainfo, ObjectValue> {

        @Override
        public ObjectValue apply(CollectionMetainfo input) {
            if (input == null) {
                return null;
            }
            ObjectValue.Builder optionsBuider = new ObjectValue.Builder().putValue("capped", input.isCapped())
                    .putValue("autoIndexId", false).putValue("flags", 2)
                    .putValue("storageEngine", input.getStorageEngine());
            if (input.isCapped()) {
                if (input.getMaxSize() > 0) {
                    optionsBuider.putValue("size", input.getMaxSize());
                }
                if (input.getMaxElements() > 0) {
                    optionsBuider.putValue("max", input.getMaxElements());
                }
            }

            ObjectValue.Builder builder = new ObjectValue.Builder();
            return builder.putValue("name", input.getName()).putValue("options", optionsBuider).build();
        }
    }
}