org.lucee.mongodb.DBCollectionImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.lucee.mongodb.DBCollectionImpl.java

Source

/**
 *
 * Copyright (c) 2015, Lucee Association Switzerland. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 **/
package org.lucee.mongodb;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import lucee.runtime.PageContext;
import lucee.runtime.dump.DumpData;
import lucee.runtime.dump.DumpProperties;
import lucee.runtime.dump.DumpTable;
import lucee.runtime.exp.PageException;
import lucee.runtime.type.Array;
import lucee.runtime.type.Collection.Key;
import lucee.runtime.type.Struct;

import org.lucee.mongodb.support.DBCollectionImplSupport;

import com.mongodb.AggregationOptions;
import com.mongodb.BulkWriteError;
import com.mongodb.BulkWriteException;
import com.mongodb.BulkWriteOperation;
import com.mongodb.BulkWriteResult;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.WriteConcern;

public class DBCollectionImpl extends DBCollectionImplSupport {

    private DBCollection coll;

    public DBCollectionImpl(DBCollection coll) {
        this.coll = coll;
    }

    @Override
    public Object get(PageContext pc, Key key, Object defaultValue) {
        throw new UnsupportedOperationException("there are no properties for this DBCollection!");
    }

    @Override
    public Object get(PageContext pc, Key key) throws PageException {
        throw new UnsupportedOperationException("there are no properties for this DBCollection!");
    }

    @Override
    public Object set(PageContext pc, Key propertyName, Object value) throws PageException {
        throw new UnsupportedOperationException("you cannot set a property to DBCollection!");
    }

    @Override
    public Object setEL(PageContext pc, Key propertyName, Object value) {
        throw new UnsupportedOperationException("you cannot set a property to DBCollection!");
    }

    @Override
    public Object call(PageContext pc, Key methodName, Object[] args) throws PageException {

        // aggregate
        if (methodName.equals("aggregate")) {
            boolean hasOptions = false;
            AggregationOptions options = null;
            int len = checkArgLength("aggregate", args, 1, -1); // no length limitation
            List<DBObject> pipeline = new ArrayList<DBObject>();
            // Pipeline array as single argument
            if (len == 1 && decision.isArray(args[0])) {
                Array arr = caster.toArray(args[0]);
                if (arr.size() == 0)
                    throw exp.createApplicationException(
                            "the array passed to the function aggregate needs at least 1 element");

                Iterator<Object> it = arr.valueIterator();
                while (it.hasNext()) {
                    pipeline.add(toDBObject(it.next()));
                }
            } else {
                // First argument is pipeline of operations, second argument is struct of options --> returns cursor!
                if (len == 2 && decision.isArray(args[0]) && decision.isStruct(args[1])) {
                    Array arr = caster.toArray(args[0]);
                    Iterator<Object> it = arr.valueIterator();
                    while (it.hasNext()) {
                        pipeline.add(toDBObject(it.next()));
                    }

                    hasOptions = true;
                    // options builder
                    AggregationOptions.Builder optbuilder = AggregationOptions.builder()
                            .outputMode(AggregationOptions.OutputMode.CURSOR);

                    DBObject dboOpts = toDBObject(args[1]);
                    if (dboOpts.containsField("allowDiskUse")) {
                        if (!decision.isBoolean(dboOpts.get("allowDiskUse")))
                            throw exp.createApplicationException("allowDiskUse in options must be boolean value");

                        optbuilder = optbuilder.allowDiskUse(caster.toBooleanValue(dboOpts.get("allowDiskUse")));
                    }
                    if (dboOpts.containsField("cursor")) {
                        if (!decision.isStruct(dboOpts.get("cursor")))
                            throw exp.createApplicationException(
                                    "cursor in options must be struct with optional key batchSize");

                        DBObject cursoropts = toDBObject(dboOpts.get("cursor"));
                        if (cursoropts.containsField("batchSize")) {
                            if (!decision.isNumeric(cursoropts.get("batchSize")))
                                throw exp.createApplicationException("cursor.batchSize in options must be integer");

                            optbuilder = optbuilder.batchSize(caster.toIntValue(cursoropts.get("batchSize")));
                        }
                    }

                    options = optbuilder.build();
                }
                // First argument is first operation, second argument is array of additional operations
                else if (len == 2 && decision.isArray(args[1])) {
                    Array arr = caster.toArray(args[1]);
                    pipeline.add(toDBObject(args[0]));
                    Iterator<Object> it = arr.valueIterator();
                    while (it.hasNext()) {
                        pipeline.add(toDBObject(it.next()));
                    }
                }
                // N arguments of pipeline operations
                else {
                    for (int i = 0; i < len; i++) {
                        pipeline.add(toDBObject(args[i]));
                    }
                }
            }

            if (hasOptions) {
                // returns Cursor - requires >= MongoDB 2.6
                return toCFML(coll.aggregate(pipeline, options));
            } else {
                // returns AggregationOutput
                return toCFML(coll.aggregate(pipeline));
            }
        }
        // count
        if (methodName.equals("count")) {
            int len = checkArgLength("count", args, 0, 1);
            if (len == 0) {
                return toCFML(coll.count());
            } else if (len == 1) {
                return toCFML(coll.count(toDBObject(args[0])));
            }
        }
        // dataSize
        if (methodName.equals("dataSize")) {
            checkArgLength("dataSize", args, 0, 0);
            return toCFML(coll.getStats().get("size"));
        }

        // distinct
        if (methodName.equals("distinct")) {
            int len = checkArgLength("distinct", args, 1, 2);
            if (len == 1) {
                return toCFML(coll.distinct(caster.toString(args[0])));
            } else if (len == 2) {
                return toCFML(coll.distinct(caster.toString(args[0]), toDBObject(args[1])));
            }
        }
        // drop
        if (methodName.equals("drop")) {
            checkArgLength("drop", args, 0, 0);
            coll.drop();
            return null;
        }

        // dropIndex
        if (methodName.equals("dropIndex")) {
            checkArgLength("dropIndex", args, 1, 1);
            DBObject dbo = toDBObject(args[0], null);
            if (dbo != null)
                coll.dropIndex(dbo);
            else
                coll.dropIndex(caster.toString(args[0]));

            return null;
        }
        // dropIndexes
        if (methodName.equals("dropIndexes")) {
            int len = checkArgLength("dropIndexes", args, 0, 1);
            if (len == 0) {
                coll.dropIndexes();
                return null;
            } else if (len == 1) {
                coll.dropIndexes(caster.toString(args[0]));
                return null;
            }
        }

        // createIndex
        if (methodName.equals("createIndex") || methodName.equals("ensureIndex")) {
            int len = checkArgLength("createIndex", args, 1, 3);
            if (len == 1) {
                DBObject dbo = toDBObject(args[0], null);
                if (dbo != null)
                    coll.createIndex(dbo);
                else
                    coll.createIndex(caster.toString(args[0]));
                return null;
            }
            if (len == 2) {
                DBObject p1 = toDBObject(args[0]);
                DBObject p2 = toDBObject(args[1], null);
                if (p2 != null)
                    coll.createIndex(p1, p2);
                else
                    coll.createIndex(p1, caster.toString(args[1]));
                return null;
            } else if (len == 3) {
                coll.createIndex(toDBObject(args[0]), caster.toString(args[1]), caster.toBooleanValue(args[2]));
                return null;
            }
        }

        // getStats
        if (methodName.equals("getStats") || methodName.equals("stats")) {
            checkArgLength("getStats", args, 0, 0);
            return toCFML(coll.getStats());
        }

        // getIndexes
        if (methodName.equals("getIndexes") || methodName.equals("getIndexInfo")) {
            checkArgLength(methodName.getString(), args, 0, 0);
            return toCFML(coll.getIndexInfo());
        }

        // getWriteConcern
        if (methodName.equals("getWriteConcern")) {
            checkArgLength("getWriteConcern", args, 0, 0);
            return toCFML(coll.getWriteConcern());
        }

        // find
        if (methodName.equals("find")) {
            int len = checkArgLength("find", args, 0, 3);
            DBCursor cursor = null;
            if (len == 0) {
                cursor = coll.find();
            } else if (len == 1) {
                cursor = coll.find(toDBObject(args[0]));
            } else if (len == 2) {
                cursor = coll.find(toDBObject(args[0]), toDBObject(args[1]));
            } else if (len == 3) {
                cursor = coll.find(toDBObject(args[0]), toDBObject(args[1])).skip(caster.toIntValue(args[2]));
            }

            return toCFML(cursor);
        }
        // findOne
        else if (methodName.equals("findOne")) {
            int len = checkArgLength("findOne", args, 0, 3);
            DBObject obj = null;
            if (len == 0) {
                obj = coll.findOne();
            } else if (len == 1) {
                DBObject arg1 = toDBObject(args[0], null);
                if (arg1 != null)
                    obj = coll.findOne(arg1);
                else
                    obj = coll.findOne(args[0]);

            } else if (len == 2) {
                DBObject arg1 = toDBObject(args[0], null);
                if (arg1 != null)
                    obj = coll.findOne(arg1, toDBObject(args[1]));
                else
                    obj = coll.findOne(args[0], toDBObject(args[1]));
            } else if (len == 3) {
                obj = coll.findOne(toDBObject(args[0]), toDBObject(args[1]), toDBObject(args[2]));
            }
            return toCFML(obj);
        }
        // findAndRemove
        if (methodName.equals("findAndRemove")) {
            checkArgLength("findAndRemove", args, 1, 1);
            DBObject obj = coll.findAndRemove(toDBObject(args[0]));
            return toCFML(obj);
        }
        // findAndModify
        if (methodName.equals("findAndModify")) {
            int len = args == null ? 0 : args.length;
            if (len != 2 && len != 3 && len != 7) {
                throw exp.createApplicationException(
                        "the function findAndModify needs 2, 3 or 7 arguments, but you have defined only " + len);
            }
            DBObject obj = null;
            if (len == 2) {
                obj = coll.findAndModify(toDBObject(args[0]), toDBObject(args[1]));
            } else if (len == 3) {
                obj = coll.findAndModify(toDBObject(args[0]), toDBObject(args[1]), toDBObject(args[2]));
            } else if (len == 7) {
                obj = coll.findAndModify(toDBObject(args[0]), toDBObject(args[1]), toDBObject(args[2]),
                        caster.toBooleanValue(args[3]), toDBObject(args[4]), caster.toBooleanValue(args[5]),
                        caster.toBooleanValue(args[6]));
            }

            return toCFML(obj);
        }

        //group
        /*
           TODO: needs GroupCommand
        if(methodName.equals("group")) {
           int len=checkArgLength("group",args,1,1);
           if(len==1){
        return toCFML(coll.group(
           toDBObject(args[0])
        ));
           }
        }*/

        // insert
        if (methodName.equals("insert")) {
            checkArgLength("insert", args, 1, 1);
            return toCFML(coll.insert(toDBObjectArray(args[0])));
        }

        // insertMany(required array documents, struct options) valid options keys are string "writeconcern", boolean "ordered"
        if (methodName.equals("insertMany")) {
            int len = checkArgLength("insertMany", args, 1, 2);
            BulkWriteOperation bulk = coll.initializeOrderedBulkOperation();
            WriteConcern wc = coll.getWriteConcern();

            if (len == 2) {
                DBObject dboOpts = toDBObject(args[1]);
                if (dboOpts.containsField("ordered")) {
                    if (!decision.isBoolean(dboOpts.get("ordered")))
                        throw exp.createApplicationException("ordered in options must be boolean value");

                    if (!caster.toBooleanValue(dboOpts.get("ordered"))) {
                        bulk = coll.initializeUnorderedBulkOperation();
                    }
                }

                if (dboOpts.containsField("writeconcern")) {
                    WriteConcern newWc = WriteConcern.valueOf(caster.toString(dboOpts.get("writeconcern")));
                    if (newWc != null) {
                        wc = newWc;
                    }
                }
            }
            Map<String, Object> result = new LinkedHashMap<String, Object>();
            BulkWriteResult bulkResult;
            List<Map> writeErrors = new ArrayList<Map>();

            Array arr = caster.toArray(args[0]);
            if (arr.size() == 0) {
                result.put("nInserted", 0);
                result.put("writeErrors", writeErrors);
                result.put("acknowledged", true);
                return toCFML(result);
            }

            Iterator<Object> it = arr.valueIterator();
            while (it.hasNext()) {
                bulk.insert(toDBObject(it.next()));
            }

            try {
                bulkResult = bulk.execute(wc);
            } catch (BulkWriteException e) {
                Map<String, Object> bulkErrorItem;
                BulkWriteError bulkError;

                bulkResult = e.getWriteResult();
                List<BulkWriteError> errors = e.getWriteErrors();

                Iterator<BulkWriteError> jj = errors.iterator();
                while (jj.hasNext()) {
                    bulkErrorItem = new LinkedHashMap<String, Object>();
                    bulkError = jj.next();
                    bulkErrorItem.put("index", (bulkError.getIndex() + 1)); // +1 so we get index of item in CFML array
                    bulkErrorItem.put("code", bulkError.getCode());
                    bulkErrorItem.put("errmsg", bulkError.getMessage());
                    bulkErrorItem.put("op", bulkError.getDetails());
                    writeErrors.add(bulkErrorItem);
                }
            }

            result.put("acknowledged", bulkResult.isAcknowledged());
            if (bulkResult.isAcknowledged()) {
                result.put("nInserted", bulkResult.getInsertedCount());
                result.put("writeErrors", writeErrors);
            }

            return toCFML(result);
        }

        // bulkWrite(required array operations, struct options) valid options keys are string "writeconcern", boolean "ordered", boolean "bypassDocumentValidation"
        // an operation is a struct with the following keys: { "operation":[insert|update|updateOne|remove|removeOne], "document":[(required if operation is insert) - a doc to insert], "query":[(optional) - the query to find for remove/update operations], "update":[(required for update/updateOne) - the update document] }
        // i.e. dbCollection.bulkWrite([
        //       {"operation":"insert", "document":{"test":"insert"}}
        //       ,{"operation":"updateOne", "query":{"_id":"foo"}, "update":{"$set":{"updated":true}}}         
        //       ,{"operation":"removeOne", "query":{"_id":"goaway"}}         
        // ],{"ordered":false})
        if (methodName.equals("bulkWrite")) {
            int len = checkArgLength("bulkWrite", args, 1, 2);
            BulkWriteOperation bulk = coll.initializeOrderedBulkOperation();
            WriteConcern wc = coll.getWriteConcern();

            if (len == 2) {
                DBObject dboOpts = toDBObject(args[1]);
                if (dboOpts.containsField("ordered")) {
                    if (!decision.isBoolean(dboOpts.get("ordered")))
                        throw exp.createApplicationException("ordered in options must be boolean value");

                    if (!caster.toBooleanValue(dboOpts.get("ordered"))) {
                        bulk = coll.initializeUnorderedBulkOperation();
                    }
                }

                if (dboOpts.containsField("bypassDocumentValidation")) {
                    if (!decision.isBoolean(dboOpts.get("bypassDocumentValidation")))
                        throw exp.createApplicationException(
                                "bypassDocumentValidation in options must be boolean value");

                    bulk.setBypassDocumentValidation(
                            caster.toBooleanValue(dboOpts.get("bypassDocumentValidation")));
                }

                if (dboOpts.containsField("writeconcern")) {
                    WriteConcern newWc = WriteConcern.valueOf(caster.toString(dboOpts.get("writeconcern")));
                    if (newWc != null) {
                        wc = newWc;
                    }
                }
            }
            Map<String, Object> result = new LinkedHashMap<String, Object>();
            BulkWriteResult bulkResult;
            List<Map> writeErrors = new ArrayList<Map>();

            Array arr = caster.toArray(args[0]);
            if (arr.size() == 0) {
                result.put("nInserted", 0);
                result.put("nMatched", 0);
                result.put("nModified", 0);
                result.put("nRemoved", 0);
                result.put("writeErrors", writeErrors);
                result.put("acknowledged", true);
                return toCFML(result);
            }

            Iterator<Object> it = arr.valueIterator();
            while (it.hasNext()) {

                DBObject operation = toDBObject(it.next());

                if (operation.get("operation") == "update") {
                    // do stuff to add update operation
                    bulk.find(toDBObject(operation.get("query"))).update(toDBObject(operation.get("update")));
                } else if (operation.get("operation") == "updateOne") {
                    // do stuff to add updateOne operation
                    bulk.find(toDBObject(operation.get("query"))).updateOne(toDBObject(operation.get("update")));
                } else if (operation.get("operation") == "remove") {
                    // do stuff to add remove operation
                    bulk.find(toDBObject(operation.get("query"))).remove();
                } else if (operation.get("operation") == "removeOne") {
                    // do stuff to add removeOne operation
                    bulk.find(toDBObject(operation.get("query"))).removeOne();
                } else if (operation.get("operation") == "insert") {
                    bulk.insert(toDBObject(operation.get("document")));
                }

            }

            try {
                bulkResult = bulk.execute(wc);
            } catch (BulkWriteException e) {
                Map<String, Object> bulkErrorItem;
                BulkWriteError bulkError;

                bulkResult = e.getWriteResult();
                List<BulkWriteError> errors = e.getWriteErrors();

                Iterator<BulkWriteError> jj = errors.iterator();
                while (jj.hasNext()) {
                    bulkErrorItem = new LinkedHashMap<String, Object>();
                    bulkError = jj.next();
                    bulkErrorItem.put("index", (bulkError.getIndex() + 1)); // +1 so we get index of item in CFML array
                    bulkErrorItem.put("code", bulkError.getCode());
                    bulkErrorItem.put("errmsg", bulkError.getMessage());
                    bulkErrorItem.put("op", bulkError.getDetails());
                    writeErrors.add(bulkErrorItem);
                }
            }

            result.put("acknowledged", bulkResult.isAcknowledged());
            if (bulkResult.isAcknowledged()) {
                result.put("nInserted", bulkResult.getInsertedCount());
                result.put("nMatched", bulkResult.getMatchedCount());
                result.put("nModified", bulkResult.getModifiedCount());
                result.put("nRemoved", bulkResult.getRemovedCount());
                result.put("writeErrors", writeErrors);
            }

            return toCFML(result);
        }

        //mapReduce
        if (methodName.equals("mapReduce")) {
            int len = checkArgLength("mapReduce", args, 4, 4);
            if (len == 4) {
                return toCFML(coll.mapReduce(caster.toString(args[0]), caster.toString(args[1]),
                        caster.toString(args[2]), toDBObject(args[3])));
            }
        }

        // remove
        if (methodName.equals("remove")) {
            checkArgLength("remove", args, 1, 1);
            return toCFML(coll.remove(toDBObject(args[0])));

        }

        // rename
        if (methodName.equals("rename") || methodName.equals("renameCollection")) {
            int len = checkArgLength(methodName.getString(), args, 1, 2);
            if (len == 1) {
                return toCFML(coll.rename(caster.toString(args[0])));
            } else if (len == 2) {
                return toCFML(coll.rename(caster.toString(args[0]), caster.toBooleanValue(args[1])));
            }
        }

        // save
        if (methodName.equals("save")) {
            checkArgLength("save", args, 1, 1);
            return toCFML(coll.save(toDBObject(args[0])));
        }

        // setWriteConcern
        if (methodName.equals("setWriteConcern")) {
            checkArgLength("setWriteConcern", args, 1, 1);
            WriteConcern wc = WriteConcern.valueOf(caster.toString(args[0]));
            if (wc != null) {
                coll.setWriteConcern(wc);
            }
            return null;
        }

        // storageSize
        if (methodName.equals("storageSize")) {
            checkArgLength("storageSize", args, 0, 0);
            return toCFML(coll.getStats().get("storageSize"));
        }

        // totalIndexSize
        if (methodName.equals("totalIndexSize")) {
            checkArgLength("totalIndexSize", args, 0, 0);
            return toCFML(coll.getStats().get("totalIndexSize"));
        }

        // update
        if (methodName.equals("update")) {
            int len = checkArgLength("update", args, 2, 4);
            if (len == 2) {
                return toCFML(coll.update(toDBObject(args[0]), toDBObject(args[1])));
            } else if (len == 3) {
                return toCFML(coll.update(toDBObject(args[0]), toDBObject(args[1]), caster.toBooleanValue(args[2]),
                        false));
            } else if (len == 4) {
                return toCFML(coll.update(toDBObject(args[0]), toDBObject(args[1]), caster.toBooleanValue(args[2]),
                        caster.toBooleanValue(args[3])));
            }
        }

        String functionNames = "aggregate,count,dataSize,distinct,drop,dropIndex,dropIndexes,createIndex,stats,getIndexes,getWriteConcern,find,findOne,findAndRemove,findAndModify,"
                + "group,insert,insertMany,bulkWrite,mapReduce,remove,rename,save,setWriteConcern,storageSize,totalIndexSize,update";

        throw exp.createApplicationException(
                "function " + methodName + " does not exist existing functions are [" + functionNames + "]");
    }

    @Override
    public Object callWithNamedValues(PageContext pc, Key methodName, Struct args) throws PageException {
        throw new UnsupportedOperationException("named arguments are not supported yet!");
    }

    @Override
    public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
        DBCursor cursor = coll.find();
        Iterator<DBObject> it = cursor.iterator();
        DumpTable table = new DumpTable("struct", "#339933", "#8e714e", "#000000");
        table.setTitle("DBCollection");

        maxlevel--;
        DBObject obj;
        while (it.hasNext()) {
            obj = it.next();
            table.appendRow(0, __toDumpData(toCFML(obj), pageContext, maxlevel, dp));
        }
        return table;
    }

    public DBCollection getDBCollection() {
        return coll;
    }

}