org.basex.modules.nosql.MongoDB.java Source code

Java tutorial

Introduction

Here is the source code for org.basex.modules.nosql.MongoDB.java

Source

package org.basex.modules.nosql;

import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.basex.query.QueryException;
import org.basex.query.func.FuncOptions;
import org.basex.query.value.Value;
import org.basex.query.value.item.Bln;
import org.basex.query.value.item.Int;
import org.basex.query.value.item.Item;
import org.basex.query.value.item.QNm;
import org.basex.query.value.item.Str;
import org.basex.query.value.map.Map;
import org.basex.query.value.type.SeqType;

import com.mongodb.*;
import com.mongodb.MapReduceCommand.OutputType;
import com.mongodb.util.JSON;
import com.mongodb.util.JSONParseException;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoException;

/**
 * This is the primary class for MongoDb processing in Basex.
 *
 * @author BaseX Team 2005-14, BSD License
 * @author Prakash Thapa
 */
public class MongoDB extends Nosql {
    /** URL of MongDB module. */
    private static final String MONGO_URL = "http://basex.org/modules/mongodb";
    /** QName of MongoDB options. */
    private static final QNm Q_MONGODB = QNm.get("mongodb", "options", MONGO_URL);
    /** mongoclient instances. */
    private HashMap<String, MongoClient> mongoClients = new HashMap<>();
    /** DB instances instances. */
    private HashMap<String, DB> dbs = new HashMap<>();
    /** Mongo Options instances. */
    private HashMap<String, NosqlOptions> mongopts = new HashMap<>();

    /**
     * Connect MongoDB with it's url in the format of mongodb://root:root@localhost/test.
     * @param  MongoDB URL for connection
     * @return  Str Key of HashMap that contains all DB Object of Mongodb
     * connection instances.
     * @throws QueryException query exception
     */
    //    public Str connection(final Str url) throws QueryException {
    //        return connection(url, null);
    //    }
    /**
     * Connect parameters in map like: {"host":"localhost","port":27017,
     * "database":"test", "username":"user", "password":"pass"}.
     * @param connectionMap Map
     * @return Str Key of HashMap that contains all DB Object of Mongodb
     * connection instances.
     * @throws QueryException query exception
     */
    public Str connect(final Map connectionMap) throws QueryException {
        final NosqlOptions opts = new NosqlOptions();
        if (connectionMap != null) {
            new FuncOptions(Q_MONGODB, null).parse(connectionMap, opts);
        }
        if (opts.get(NosqlOptions.URL) != null) {
            return connect(Str.get(opts.get(NosqlOptions.URL)), connectionMap);
        }
        String handler = "Client" + mongoClients.size();
        try {
            MongoClient mongoClient = new MongoClient(opts.get(NosqlOptions.HOST), opts.get(NosqlOptions.PORT));
            mongoClients.put(handler, mongoClient);
            char[] pass = (opts.get(NosqlOptions.PASSWORD) != null) ? opts.get(NosqlOptions.PASSWORD).toCharArray()
                    : null;
            return mongoConnect(handler, opts.get(NosqlOptions.DATABASE), opts.get(NosqlOptions.USERNAME), pass,
                    connectionMap);
        } catch (final MongoException ex) {
            throw MongoDBErrors.mongoExceptionError(ex);
        } catch (UnknownHostException ex) {
            throw MongoDBErrors.generalExceptionError(ex);
        } catch (final NullPointerException ex) {
            throw MongoDBErrors.connectionNullException();

        }
    }

    /**
     * Mongodb connection with options.
     * @param url mongodb url like: "mongodb://127.0.0.1:27017/basex"
     * @param options nosql options
     * @return Str
     * @throws QueryException query exception
     */
    public Str connect(final Str url, final Map options) throws QueryException {
        MongoClientURI uri = new MongoClientURI(url.toJava());
        String handler = "mongoClient" + mongoClients.size();
        try {
            MongoClient mongoClient = new MongoClient(uri);
            mongoClients.put(handler, mongoClient);
            return mongoConnect(handler, uri.getDatabase(), uri.getUsername(), uri.getPassword(), options);
        } catch (final MongoException ex) {
            throw MongoDBErrors.mongoExceptionError(ex);
        } catch (UnknownHostException ex) {
            throw MongoDBErrors.generalExceptionError(ex);
        }
    }

    /**
     * Mongodb connection with separate Hostname, port and database.
     * @param host hostname of server.
     * @param port Port Number
     * @param database Database name.
     * @return Str key of Hashmap that contains all DB Instances
     * @throws QueryException query exception
     */
    public Str connect(final Str host, final Int port, final Str database) throws QueryException {
        return connection(host, port, database, null, null, null);
    }

    /**
     * Mongodb connection with separate Hostname, port and database.
     * @param host hostname of server.
     * @param port Port Number
     * @param database Database name.
     * @param options other nosql options like {'type':'json'}
     * @return Str key of Hashmap that contains all DB Instances
     * @throws QueryException query exception
     */
    public Str connection(final Str host, final Int port, final Str database, final Map options)
            throws QueryException {
        return connection(host, port, database, options, null, null);
    }

    /**
     * Mongodb connection with separate Hostname, port and database.
     * @param host hostname of server.
     * @param port Port Number
     * @param database name of databse to be connected
     * @param options other nosql options like {'type':'json'}
     * @param username username of Mongodb
     * @param password password of Mongodb
     * @return Str key of Hashmap that contains all DB Instances
     * @throws QueryException query exception
     */
    public Str connection(final Str host, final Int port, final Str database, final Map options, final Str username,
            final Str password) throws QueryException {
        String handler = "Client" + mongoClients.size();
        try {
            MongoClient mongoClient = new MongoClient(host.toJava(), (int) port.itr());
            mongoClients.put(handler, mongoClient);
            char[] pass = (password != null) ? password.toJava().toCharArray() : null;
            String user = (String) ((username != null) ? username.toJava() : username);
            return mongoConnect(handler, database.toJava(), user, pass, options);
        } catch (final MongoException ex) {
            throw MongoDBErrors.mongoExceptionError(ex);
        } catch (UnknownHostException ex) {
            throw MongoDBErrors.generalExceptionError(ex);
        }
    }

    /**
     * This method check if the database handler is still connected or not.
     * @param handler database handler.
     * @return Bln true or false
     * @throws QueryException query exception
     */
    public Bln isconnected(final Str handler) throws QueryException {
        try {
            final MongoClient client = (MongoClient) getDbHandler(handler).getMongo();
            if (client == null) {
                return Bln.FALSE;
            }
            return Bln.TRUE;
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        }
    }

    /**
     * This method take key of Hashmap, create DB object and store to another
     * Hashmap.
     * @param mongoClientHandler Key of Hashmap that contains all Mongoclient instances
     * @param database name of database to be connect
     * @param username user's name for mongodb Connect
     * @param password password of user to connect Mongodb
     * @param options Other nosql options like {"type":"json"} and so on
     * @return Str key of Hashmap that contains all DB Instances
     * @throws QueryException query exception
     */
    private Str mongoConnect(final String mongoClientHandler, final String database, final String username,
            final char[] password, final Map options) throws QueryException {
        final NosqlOptions opts = new NosqlOptions();
        if (options != null) {
            new FuncOptions(Q_MONGODB, null).parse(options, opts);
        }
        MongoClient mongoClient = mongoClients.get(mongoClientHandler);
        final String dbh = "DB" + dbs.size();
        try {
            DB db = mongoClient.getDB(database);
            if (username != null && password != null) {
                boolean auth = db.authenticate(username, password);
                if (!auth) {
                    throw MongoDBErrors.unauthorised();
                }
            }
            dbs.put(dbh, db);
            if (options != null) {
                mongopts.put(dbh, opts);
            }
            return Str.get(dbh);
        } catch (final MongoException ex) {
            throw MongoDBErrors.mongoExceptionError(ex);
        }
    }

    /**
     * This Method take Map as parameters and create array of <code>DBObject</code>
     * of MongoDB in first level like {"a":{"b":"c"},"x":{"y":"z"}} here there
     * are two array with key "a" and "x" other will be be the key value parameter
     * inBasicDBObject.
     *  * <blockquote><pre>
     *  DBObject obj = new BasicDBObject();
     *  obj.put( "foo", "bar" );
     *  </pre></blockquote>
     * @param map Basex Map
     * @return array of DBObject
     */
    private DBObject[] mapToDBObjectArray(final Map map) {
        if ((map != null) && (!map.isEmpty())) {
            try {
                final Value keys = map.keys();
                DBObject[] dbObject = null;
                int length = (int) keys.size();
                if (length > 0) {
                    dbObject = new BasicDBObject[length];
                    int i = 0;
                    for (Item key : keys) {
                        final Value value = map.get(key, null);
                        DBObject singleObject = new BasicDBObject();
                        if (value instanceof Map) {
                            singleObject.put(((Str) key).toJava(), this.mapToDBObject((Map) value));
                        } else if (value.seqType().instanceOf(SeqType.ITR_OM)) {
                            long l = ((Item) value).itr(null);
                            singleObject.put(((Str) key).toJava(), l);
                        } else {
                            singleObject.put(((Str) key).toJava(), value.toJava());
                        }
                        dbObject[i] = singleObject;
                        i++;
                    }
                }
                return dbObject;
            } catch (Exception e) {
                MongoDBErrors.generalExceptionError(e);
            }
        }
        return null;
    }

    /**
     * This Method convert Basex Map to MongoDB's DBObject.
     * {"foo":"bar"}
     * <blockquote><pre>
     *  DBObject obj = new BasicDBObject();
     *  obj.put( "foo", "bar" );
     *  </pre></blockquote>
     * @param map Basex's Map
     * @return DBObject
     * @throws QueryException query exception
     */
    private DBObject mapToDBObject(final Map map) throws QueryException {
        if (map != null) {
            final DBObject dbObject = new BasicDBObject();
            final Value keys = map.keys();
            for (Item key : keys) {
                final Value value = map.get(key, null);
                if (value instanceof Map) {
                    dbObject.put(((Str) key).toJava(), this.mapToDBObject((Map) value));
                } else if (value.seqType().instanceOf(SeqType.ITR_OM)) {
                    long l = ((Item) value).itr(null);
                    dbObject.put(((Str) key).toJava(), l);
                } else {
                    dbObject.put(((Str) key).toJava(), value.toJava());
                }
            }
            return dbObject;
        }
        return null;
    }

    /**
     * This Method gives the DB Handler from Hashmap with given Key.
     * @param handler database handler key for Hashmap that contains all DB objects
     * @return DB Object
     * @throws QueryException query exception
     */
    protected DB getDbHandler(final Str handler) throws QueryException {
        final DB db = dbs.get(handler.toJava());
        if (db == null)
            throw MongoDBErrors.mongoDBError(handler.toJava());
        return db;
    }

    /**
     * Get Mongodb Options for particular database handler.
     * @param handler database handler Database handler
     * @return MongoOptions object
     */
    private NosqlOptions getMongoOption(final Str handler) {
        NosqlOptions opt = mongopts.get(handler.toJava());
        if (opt != null)
            return opt;
        return null;
    }

    /**
     * All the result from Mongodb will come to this Method in the form of Json String.
     * First it checks the assigned NOSQL {@link NosqlOptions} options and then
     * return the final result {@link #finalResult(Str, NosqlOptions)} process by parent
     * class[{@link Nosql}].
     * @param handler database handler Database handler
     * @param json Str object that contains Json string
     * @return Item
     * @throws Exception exception
     */
    private Item returnResult(final Str handler, final Str json) throws Exception {
        NosqlOptions opt = getMongoOption(handler);
        if (json != null) {
            if (opt != null) {
                return finalResult(json, opt);
            }
            return finalResult(json, null);
        }
        return null;
    }

    /**
     * Convert collection result(DBCursor) into Item {@link Item} element.
     * @param handler database handler
     * @param cursor DBcursor
     * @return Item
     * @throws QueryException query exception
     */
    private Item cursorToItem(final Str handler, final DBCursor cursor) throws QueryException {
        if (cursor != null) {
            try {
                if (cursor.count() == 1) {
                    Iterator<DBObject> row = cursor.iterator();
                    return objectToItem(handler, row.next());
                }
                final Str json = Str.get(JSON.serialize(cursor));
                return returnResult(handler, json);
            } catch (final Exception ex) {
                throw MongoDBErrors.generalExceptionError(ex);
            }
        }
        return null;
    }

    /**
     * Convert collection List<DBObject>  into Item {@link Item} element.
     * @param handler database handler
     * @param result DBcursor
     * @return Item
     * @throws QueryException query exception
     */
    private Item cursorListToItem(final Str handler, final List<DBObject> result) throws QueryException {
        if (result != null) {
            try {
                final Str json = Str.get(JSON.serialize(result));
                return returnResult(handler, json);
            } catch (final Exception ex) {
                throw MongoDBErrors.generalExceptionError(ex);
            }
        }
        return null;
    }

    /**
     * Convert collection DBObject into Item {@link Item} element.
     * @param handler databse handler
     * @param object DBObject  (one row result)
     * @return Item
     * @throws QueryException query exception
     */
    private Item objectToItem(final Str handler, final DBObject object) throws QueryException {
        if (object != null) {
            try {
                final Str json = Str.get(JSON.serialize(object));
                return returnResult(handler, json);

            } catch (final Exception ex) {
                throw MongoDBErrors.generalExceptionError(ex);
            }
        }
        return null;
    }

    /**
     * Take string as Str and return DBObject of mongodb.
     * @param item item(Map or Str)
     * @return DBObject
     * @throws QueryException query exception
     */
    protected DBObject getDbObjectFromItem(final Item item) throws QueryException {
        try {
            if (item instanceof Map) {
                return mapToDBObject((Map) item);
            } else if (item instanceof Str) {
                final String string = itemToString(item);
                return (DBObject) JSON.parse(string);
            } else {
                throw MongoDBErrors.generalExceptionError("Number Expected for key '");
            }
        } catch (JSONParseException e) {
            throw MongoDBErrors.jsonFormatError(item.toJava());
        }
    }

    /**
     * Return all the collections in current database.
     * @param handler database handler DB handler.
     * @return Item
     * @throws QueryException query exception
     */
    public Item collections(final Str handler) throws QueryException {
        final DB db = getDbHandler(handler);
        Set<String> col = db.getCollectionNames();
        BasicDBObject collection = new BasicDBObject("collections", col);
        try {
            return objectToItem(handler, collection);
        } catch (JSONParseException e) {
            throw MongoDBErrors.jsonFormatError(col);
        }
    }

    /**
     * MongoDB find() without any parameters. eg. db.collections.find()
     * @param handler database handler Database handler
     * @param col collection name
     * @return result in xml element
     * @throws QueryException query exception
     */
    public Item find(final Str handler, final Item col) throws QueryException {
        return find(handler, col, null, null, null);
    }

    /**
     * MongoDB find() with query. eg. db.collections.find({'_id':2})
     * @param handler database handler Database handler
     * @param col collection collection
     * @param query conditions
     * @return Item
     * @throws QueryException query exception
     */
    public Item find(final Str handler, final Item col, final Item query) throws QueryException {
        return find(handler, col, query, null, null);
    }

    /**
     * MongoDB find() Query and Projection.
     * @param handler database handler Database handler
     * @param col collection collection
     * @param query conditions
     * @param opt options in Map like: {"limit":2}
     * @return Item
     * @throws QueryException query exception
     */
    public Item find(final Str handler, final Str col, final Item query, final Item opt) throws QueryException {
        return find(handler, col, query, opt, null);
    }

    /**
     * MongoDB find with all parameters.
     * @param handler database handler Database handler
     * @param col collection collection
     * @param query Query parameters
     * @param opt options in Map like: {"limit":2}
     * @param projection projection (selection field)
     * @return Item
     * @throws QueryException query exception
     */
    public Item find(final Str handler, final Item col, final Item query, final Item opt, final Item projection)
            throws QueryException {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            DBObject p = null;
            if (opt != null && opt instanceof Str) {
                p = getDbObjectFromItem(opt);
            } else if (projection != null && projection instanceof Str) {
                p = getDbObjectFromItem(projection);
            }
            final DBObject q = query != null ? getDbObjectFromItem(query) : null;
            final DBCollection coll = db.getCollection(itemToString(col));
            final DBCursor cursor = coll.find(q, p);
            Map options = null;
            options = (opt != null && opt instanceof Map) ? (Map) opt
                    : (projection != null && projection instanceof Map) ? (Map) projection : null;
            if (options != null) {
                Value keys = options.keys();
                for (final Item key : keys) {
                    if (!(key instanceof Str))
                        throw MongoDBErrors.generalExceptionError("String expected " + key.toJava());
                    final String k = ((Str) key).toJava();
                    final Value v = options.get(key, null);
                    if (v instanceof Str || v.seqType().instanceOf(SeqType.ITR)) {
                        if (k.equals(LIMIT)) {
                            if (v.seqType().instanceOf(SeqType.ITR_OM)) {
                                long l = ((Item) v).itr(null);
                                cursor.limit((int) l);
                            } else {
                                throw MongoDBErrors
                                        .generalExceptionError("Number Expected for key '" + key.toJava() + "'");
                            }
                        } else if (k.equals(SKIP)) {
                            //cursor.skip(Token.toInt(v));
                        } else if (k.equals(SORT)) {
                            BasicDBObject sort = new BasicDBObject(k, v);
                            sort.append("name", "-1");
                            cursor.sort(sort);
                        } else if (k.equals(COUNT)) {
                            int count = cursor.count();
                            BasicDBObject res = new BasicDBObject();
                            res.append("count", count);
                            return objectToItem(handler, res);
                        } else if (k.equals(EXPLAIN)) {
                            DBObject result = cursor.explain();
                            return objectToItem(handler, result);
                        }
                    } else if (v instanceof Map) {
                    } else {
                        throw MongoDBErrors.generalExceptionError("Invalid value 2...");
                    }
                }
            }
            return cursorToItem(handler, cursor);
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e.getMessage());
        } finally {
            db.requestDone();
        }
    }

    /**
     * Mongodb findAndUpdate().
     * @param handler database handler Database handler
     * @param col collection collection
     * @param query Query parameters
     * @param update update parameters
     * @return Item
     * @throws QueryException Query exception.
     */
    public Item findAndModify(final Str handler, final Item col, final Item query, final Item update)
            throws QueryException {
        final DB db = getDbHandler(handler);
        try {
            final DBObject q = query != null ? getDbObjectFromItem(query) : null;
            final DBObject u = query != null ? getDbObjectFromItem(update) : null;
            final DBObject result = db.getCollection(itemToString(col)).findAndModify(q, u);
            return objectToItem(handler, result);
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e.getMessage());
        } finally {
            db.requestDone();
        }
    }

    /**
     * Modifies and returns a single document. By default, the returned document
     * does not include the modifications made on the update.
     * @param handler database handler Database handler
     * @param col collection collection
     * @param options Map
     * @return Item.
     * @throws QueryException Query exception.
     */
    public Item findAndModify(final Str handler, final Item col, final Map options) throws QueryException {
        final DB db = getDbHandler(handler);
        DBObject query = null;
        DBObject fields = null;
        DBObject sort = null;
        DBObject update = null;
        boolean returnNew = false;
        boolean upsert = false;
        boolean remove = false;
        for (Item key : options.keys()) {
            final String k = ((Str) key).toJava();
            final Value v = options.get(key, null);
            if (k.toLowerCase().equals(QUERY)) {
                query = getDbObjectFromItem((Item) v);
            } else if (k.toLowerCase().equals(FIELDS)) {
                fields = getDbObjectFromItem((Item) v);
            } else if (k.toLowerCase().equals(SORT)) {
                sort = getDbObjectFromItem((Item) v);
            } else if (k.toLowerCase().equals(UPDATE)) {
                update = getDbObjectFromItem((Item) v);
            } else if (k.toLowerCase().equals(NEW)) {
                if (v instanceof Bln) {
                    returnNew = ((Bln) v).toJava();
                } else {
                    throw MongoDBErrors.generalExceptionError("xs:boolean is expected for 'returnnew'");
                }
            } else if (k.toLowerCase().equals(UPSERT)) {
                if (v instanceof Bln) {
                    returnNew = ((Bln) v).toJava();
                } else {
                    throw MongoDBErrors.generalExceptionError("xs:boolean is expected for 'upsert'");
                }
            }
        }
        if (query == null)
            throw MongoDBErrors.findAndModifyQuery();
        if (update == null)
            throw MongoDBErrors.findAndModifyUpdate();
        db.requestStart();
        try {
            DBObject result = db.getCollection(itemToString(col)).findAndModify(query, fields, sort, remove, update,
                    returnNew, upsert);
            return objectToItem(handler, result);
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e.getMessage());
        } finally {
            db.requestDone();
        }
    }

    /**
     * Mongodb's findOne() function.
     * @param handler database handler
     * @param col collection
     * @return Item
     * @throws QueryException query exception
     */
    public Item findOne(final Str handler, final Item col) throws QueryException {
        return findOne(handler, col, null, null);
    }

    /**
     * Mongodb's findOne({'_id':2}) function with query.
     * @param handler database handler
     * @param col collection
     * @param query selection query
     * @return Item
     * @throws QueryException query exception
     */
    public Item findOne(final Str handler, final Item col, final Item query) throws QueryException {
        return findOne(handler, col, query, null);
    }

    /**
     * findOne with query and projection projection.
     * @param handler database handler
     * @param col collection
     * @param query selection query
     * @param projection  projection
     * @return Item
     * @throws QueryException query exception
     */
    public Item findOne(final Str handler, final Item col, final Item query, final Item projection)
            throws QueryException {

        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            final DBObject p = projection != null ? getDbObjectFromItem(projection) : null;
            final DBObject q = query != null ? getDbObjectFromItem(query) : null;
            final DBCollection coll = db.getCollection(itemToString(col));
            final DBObject cursor = coll.findOne(q, p);
            return objectToItem(handler, cursor);
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e.getMessage());
        } finally {
            db.requestDone();
        }
    }

    /**
     * Insert data in MongoDB.
     * @param handler database handler DB Handler
     * @param col collection name
     * @param insertString string to insert in json formart or in Basex's Map
     * @return Item result from Mongodb.
     * @throws Exception exception
     */
    public Item insert(final Str handler, final Str col, final Str insertString) throws Exception {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            DBObject obj = getDbObjectFromItem(insertString);
            WriteResult wr = db.getCollection(col.toJava()).insert(obj);
            return returnResult(handler, Str.get(wr.toString()));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(db.getLastError().getString("err"));
        } finally {
            db.requestDone();
        }
    }

    /**
     * Mongodb update with two parameters like update({},{}).
     * @param handler database handler
     * @param col collection collection's name
     * @param query selection query
     * @param updatestring Json Str or Basex's Map
     * @return Item
     * @throws Exception exception
     */
    public Item update(final Str handler, final Item col, final Item query, final Item updatestring)
            throws Exception {
        return update(handler, col, query, updatestring, null, null);
    }

    /**
     * Mongodb update with 4 parameters like update({},{}, upsert, multi).
     * @param handler database handler Db Handler string
     * @param col collection name
     * @param query selection query
     * @param updatestring String to be updated
     * @param upsert true/false for mongodb upsert(Json Str or Map)
     * @param multi true/false for mongodb multi
     * @return Item
     * @throws Exception exception
     */
    public Item update(final Str handler, final Item col, final Item query, final Item updatestring,
            final Bln upsert, final Bln multi) throws Exception {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            DBObject q = getDbObjectFromItem(query);
            DBObject updateValue = getDbObjectFromItem(updatestring);
            WriteResult wr;
            if (upsert != null && multi != null) {
                wr = db.getCollection(itemToString(col)).update(q, updateValue, upsert.toJava(), multi.toJava());
            } else {
                wr = db.getCollection(itemToString(col)).update(q, updateValue);
            }
            return returnResult(handler, Str.get(wr.toString()));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(db.getLastError().getString("err"));
        } finally {
            db.requestDone();
        }
    }

    /**
     * Mongodb Save function.
     * @param handler database handler DB handler
     * @param col collection name
     * @param saveStr string to save(Map or Josn)
     * @return Item
     * @throws Exception exception
     */
    public Item save(final Str handler, final Str col, final Item saveStr) throws Exception {
        return save(handler, col, saveStr, null);
    }

    /**
     * Mongodb Save function.
     * @param handler database handler DB handler
     * @param col collection name
     * @param saveStr string to save(Map or Josn)
     * @param options other writeconcern options
     * @return Item result as item
     * @throws Exception exception
     */
    public Item save(final Str handler, final Str col, final Item saveStr, final Map options) throws Exception {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            if (options == null) {
                WriteResult wr = db.getCollection(col.toJava()).save(getDbObjectFromItem(saveStr));
                return returnResult(handler, Str.get(wr.toString()));
            }
            //TODO write concern from the options
            WriteResult wr = db.getCollection(col.toJava()).save(getDbObjectFromItem(saveStr), WriteConcern.SAFE);
            return returnResult(handler, Str.get(wr.toString()));

        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(db.getLastError().getString("err"));
        } finally {
            db.requestDone();
        }
    }

    /**
     * Mongodb remove(). This will delete the document with specified query.
     * @param handler database handler
     * @param col collection name;
     * @param query Query to select document.(Map or Json Str)
     * @throws QueryException query exception
     */
    public void remove(final Str handler, final Item col, final Item query) throws QueryException {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            //TODO write concern.
            db.getCollection(itemToString(col)).remove(getDbObjectFromItem(query));
            DBObject err = db.getLastError();
            if (err != null) {
                throw MongoDBErrors.generalExceptionError(err.get("err").toString());
            }
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * Mongodb Aggregate function with single aggregation parameter.
     * @param handler database handler database handler
     * @param col collection name
     * @param first Only one parameter of Aggregate function.
     * @return Item
     * @throws Exception exception
     */
    public Item aggregate(final Str handler, final Item col, final Item first) throws Exception {
        return aggregate(handler, col, first, null);
    }

    /**
     * This method is for aggregating with all pipeline options. All the options
     * should be given in sequence like: ('$group:{..}',...).
     * @param handler database handler database handler
     * @param col collection name
     * @param first aggregation compulsary
     * @param additionalOps other pipeline options in sequence.
     * @return Item
     * @throws Exception exception
     */
    public Item aggregate(final Str handler, final Item col, final Item first, final Value additionalOps)
            throws Exception {
        final DB db = getDbHandler(handler);
        AggregationOutput agg;
        DBObject[] pipeline = null;
        if (additionalOps != null && (!additionalOps.isEmpty())) {
            if (additionalOps instanceof Map) {
                pipeline = mapToDBObjectArray((Map) additionalOps);
            } else {
                int length = (int) additionalOps.size();
                if (length > 0) {
                    pipeline = new BasicDBObject[length];
                    int i = 0;
                    for (Item x : additionalOps) {
                        pipeline[i++] = getDbObjectFromItem(x);
                    }
                }
            }
        }
        db.requestStart();
        try {
            if (additionalOps != null && (!additionalOps.isEmpty())) {
                agg = db.getCollection(itemToString(col)).aggregate(getDbObjectFromItem(first), pipeline);
            } else {
                agg = db.getCollection(itemToString(col)).aggregate(getDbObjectFromItem(first));
            }
            final Iterable<DBObject> d = agg.results();
            return returnResult(handler, Str.get(JSON.serialize(d)));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e.getMessage());
        } finally {
            db.requestDone();
        }

    }

    /**
     * count numbers of documents in a collection.
     * @param handler database handler
     * @param col collection
     * @return number
     * @throws QueryException query exception
     */
    public long count(final Str handler, final Item col) throws QueryException {
        final DB db = getDbHandler(handler);
        long count = db.getCollection(itemToString(col)).count();
        return count;
    }

    /**
     * Copy data from One collection to another collection within same Database.
     * @param handler database handler
     * @param source Source collection name
     * @param dest Destination Collection name.
     * @throws QueryException query exception
     */
    public void copy(final Str handler, final Item source, final Item dest) throws QueryException {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            List<DBObject> cursor = db.getCollection(itemToString(source)).find().toArray();
            db.getCollection(itemToString(dest)).insert(cursor);
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * Copy collection from one Database insert to another database.
     * @param handler database handler Mongodb Connection for source database
     * @param source source collection
     * @param handlerDest Mongodb Connection for Destination database
     * @param dest Destination Collection
     * @throws QueryException query exception
     */
    public void copy(final Str handler, final Item source, final Str handlerDest, final Item dest)
            throws QueryException {
        final DB db = getDbHandler(handler);
        final DB dbDestionation = getDbHandler(handlerDest);
        db.requestStart();
        try {
            List<DBObject> cursor = db.getCollection(itemToString(source)).find().toArray();
            dbDestionation.getCollection(itemToString(dest)).drop();
            dbDestionation.getCollection(itemToString(dest)).insert(cursor);
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * rename collection.
     * @param handler database handler
     * @param col collection name
     * @param newCollection new collection name.
     * @throws QueryException query exception
     */
    public void rename(final Str handler, final Item col, final Str newCollection) throws QueryException {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            db.getCollection(itemToString(col)).rename(itemToString(newCollection));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * Drop collection from a database.
     * @param handler database handler
     * @param col collection name
     * @throws QueryException query exception
     */
    public void drop(final Str handler, final Item col) throws QueryException {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            db.getCollection(itemToString(col)).drop();
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * MongoDB runCommand() function.
     * @param handler database handler
     * @param command Command to Execute
     * @return Item
     * @throws Exception exception
     */
    public Item runCommand(final Str handler, final Item command) throws Exception {
        return runCommand(handler, command, null);
    }

    /**
     * MongoDB runCommand() function with integer as parameter.
     * @param handler database handler
     * @param command command to execute in Map or  JSON or simply String
     * @param options integer options.
     * @return Item
     * @throws Exception exception
     */
    public Item runCommand(final Str handler, final Item command, final Int options) throws Exception {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            CommandResult result = null;
            DBObject cmd = getDbObjectFromItem(command);
            if (options != null) {
                result = db.command(cmd, (DBEncoder) options.toJava());
            } else {
                result = db.command(cmd);
            }
            return returnResult(handler, Str.get(result.toString()));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * list all the indexes of cursor.
     * @param handler database handler
     * @param col collection name of collection
     * @return Item
     * @throws QueryException query exception
     */
    public Item getindexes(final Str handler, final Str col) throws QueryException {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            List<DBObject> result = db.getCollection(itemToString(col)).getIndexInfo();
            return cursorListToItem(handler, result);
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * Create Index in specified field.
     * @param handler database handler
     * @param col collection name of collection
     * @param indexStr string to create index json or Map
     * @throws QueryException query exception
     */
    public void ensureIndex(final Str handler, final Str col, final Item indexStr) throws QueryException {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            db.getCollection(itemToString(col)).ensureIndex(getDbObjectFromItem(indexStr));
            //return returnResult(handler, Str.get(result.toString()));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * drop Index in specified field.
     * @param handler database handler
     * @param col collection name of collection
     * @param indexStr string to create index Json or Map
     * @throws QueryException query exception
     */
    public void dropIndex(final Str handler, final Str col, final Item indexStr) throws QueryException {
        final DB db = getDbHandler(handler);
        db.requestStart();
        try {
            db.getCollection(itemToString(col)).dropIndex(getDbObjectFromItem(indexStr));
            //return returnResult(handler, Str.get(result.toString()));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * take DB handler as parameter and get MongoClient and then close it.
     * @param handler database handler DB handler
     * @throws QueryException query exception
     */
    public void disconnect(final Str handler) throws QueryException {
        String ch = handler.toJava();
        try {
            final MongoClient client = (MongoClient) getDbHandler(handler).getMongo();
            if (client == null)
                throw MongoDBErrors.mongoDBError(ch);
            client.close();
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        }
    }

    /**
     * This method is implemented for MongoDB Mapreduce.
     * @param handler database handler
     * @param col collection name
     * @param map Map method
     * @param reduce Reduce Method
     * @return Item
     * @throws Exception exception
     */
    public Item mapreduce(final Str handler, final Str col, final Str map, final Str reduce) throws Exception {
        return mapreduce(handler, col, map, reduce, null, null, null);
    }

    /**
     * Mongodb Mapreduce function with 3 parameters.
     * @param handler database handler
     * @param col collection name
     * @param map Map method
     * @param reduce Reduce Method
     * @param finalalize mongodb finalize parameter
     * @return Items
     * @throws Exception exception
     */
    public Item mapreduce(final Str handler, final Str col, final Str map, final Str reduce, final Item finalalize)
            throws Exception {
        return mapreduce(handler, col, map, reduce, finalalize, null, null);
    }

    /**
     * Mongodb Mapreduce function with 4 parameters.
     * @param handler database handler
     * @param col collection name
     * @param map Map method
     * @param reduce Reduce Method
     * @param finalalize mongodb finalize parameter
     * @param query Selection options.
     * @return Items
     * @throws Exception exception
     */
    public Item mapreduce(final Str handler, final Str col, final Str map, final Str reduce, final Item finalalize,
            final Item query) throws Exception {
        return mapreduce(handler, col, map, reduce, finalalize, query, null);
    }

    /**
     * Mongodb Mapreduce function with 5 parameters, Map, reduce and query Option.
     * @param handler database handler
     * @param col collection name
     * @param map Map method
     * @param reduce Reduce Method
     * @param finalalize mongodb finalize parameter
     * @param query Selection options.
     * @param options additional options
     * @return Item
     * @throws Exception exception
     */
    public Item mapreduce(final Str handler, final Str col, final Str map, final Str reduce, final Item finalalize,
            final Item query, final Map options) throws Exception {
        final DB db = getDbHandler(handler);
        if (map == null) {
            throw MongoDBErrors.generalExceptionError("Map function cannot be empty in Mapreduce");
        }
        final DBObject q = query != null ? getDbObjectFromItem(query) : null;
        final DBCollection collection = db.getCollection(itemToString(col));
        String out = null;
        String outType = null;
        OutputType op = MapReduceCommand.OutputType.INLINE;
        if (options != null) {
            for (Item k : options.keys()) {
                String key = (String) k.toJava();
                if (key.equals("outputs")) {
                    out = (String) options.get(k, null).toJava();
                }
                if (key.equals("outputype")) {
                    outType = (String) options.get(k, null).toJava();
                }
            }
            if (out != null) {
                if (outType.toUpperCase().equals("REPLACE")) {
                    op = MapReduceCommand.OutputType.REPLACE;
                } else if (outType.toUpperCase().equals("MERGE")) {
                    op = MapReduceCommand.OutputType.MERGE;
                } else if (outType.toUpperCase().equals("REDUCE")) {
                    op = MapReduceCommand.OutputType.REDUCE;
                }
            }
        }
        db.requestStart();
        try {
            MapReduceCommand cmd = new MapReduceCommand(collection, map.toJava(), reduce.toJava(), out, op, q);
            if (finalalize != null) {
                cmd.setFinalize((String) finalalize.toJava());
            }
            final MapReduceOutput outcmd = collection.mapReduce(cmd);
            return returnResult(handler, Str.get(JSON.serialize(outcmd.results())));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }

    /**
     * Mapreduce all functions in xquery's Map like :{"map":"function(){..}"
     * , "reduce":"function(){}"}.
     * @param handler database handler
     * @param col collection name
     * @param options all options of Mapreduce including "map" in key.
     * @return Item
     * @throws Exception exception
     */
    public Item mapreduce(final Str handler, final Str col, final Map options) throws Exception {
        if (options == null) {
            throw MongoDBErrors.generalExceptionError("Map optoins are empty");
        }
        final DB db = getDbHandler(handler);
        final DBCollection collection = db.getCollection(itemToString(col));
        String out = null;
        String outType = null;
        String map = null;
        String reduce = null;
        DBObject query = null;
        DBObject sort = null;
        int limit = 0;
        String finalalize = null;
        OutputType op = MapReduceCommand.OutputType.INLINE;
        for (Item k : options.keys()) {
            String key = (String) k.toJava();
            Value val = options.get(k, null);
            String value = (String) val.toJava();
            if (key.toLowerCase().equals(MAP)) {
                map = value;
            } else if (key.toLowerCase().equals(REDUCE)) {
                reduce = value;
            } else if (key.toLowerCase().equals(OUTPUTS)) {
                out = value;
            } else if (key.toLowerCase().equals(OUTPUTTYPE)) {
                outType = value;
            } else if (key.toLowerCase().equals(LIMIT)) {
                if (val.seqType().instanceOf(SeqType.ITR_OM)) {
                    long l = ((Item) val).itr(null);
                    limit = (int) l;
                } else {
                    throw MongoDBErrors.generalExceptionError(" Expected integer Value");
                }
            } else if (key.toLowerCase().equals(SORT)) {
                sort = getDbObjectFromItem(Str.get(value));
            } else if (key.toLowerCase().equals(QUERY)) {
                query = getDbObjectFromItem(Str.get(value));
            } else if (key.toLowerCase().equals(FINALIZE)) {
                finalalize = value;
            }
        }
        if (out != null && outType != null) {
            if (outType.toUpperCase().equals("REPLACE")) {
                op = MapReduceCommand.OutputType.REPLACE;
            } else if (outType.toUpperCase().equals("MERGE")) {
                op = MapReduceCommand.OutputType.MERGE;
            } else if (outType.toUpperCase().equals("REDUCE")) {
                op = MapReduceCommand.OutputType.REDUCE;
            }
        } else if (out != null) {
            op = MapReduceCommand.OutputType.REPLACE;
        }
        if (map == null) {
            throw MongoDBErrors.generalExceptionError("Map function cannot be empty");
        }
        db.requestStart();
        try {
            MapReduceCommand cmd = new MapReduceCommand(collection, map, reduce, out, op, query);
            if (finalalize != null) {
                cmd.setFinalize(finalalize);
            }
            if (limit != 0) {
                cmd.setLimit(limit);
            }
            if (sort != null) {
                cmd.setSort(sort);
            }
            final MapReduceOutput outcmd = collection.mapReduce(cmd);
            return returnResult(handler, Str.get(JSON.serialize(outcmd.results())));
        } catch (MongoException e) {
            throw MongoDBErrors.generalExceptionError(e);
        } finally {
            db.requestDone();
        }
    }
}