Package com.github.jmkgreen.morphia

Source Code of com.github.jmkgreen.morphia.DatastoreImpl

package com.github.jmkgreen.morphia;

import com.github.jmkgreen.morphia.annotations.*;
import com.github.jmkgreen.morphia.indexing.TextIndexCommand;
import com.github.jmkgreen.morphia.logging.Logr;
import com.github.jmkgreen.morphia.logging.MorphiaLoggerFactory;
import com.github.jmkgreen.morphia.mapping.MappedClass;
import com.github.jmkgreen.morphia.mapping.MappedField;
import com.github.jmkgreen.morphia.mapping.Mapper;
import com.github.jmkgreen.morphia.mapping.MappingException;
import com.github.jmkgreen.morphia.mapping.cache.EntityCache;
import com.github.jmkgreen.morphia.mapping.lazy.DatastoreHolder;
import com.github.jmkgreen.morphia.mapping.lazy.proxy.ProxyHelper;
import com.github.jmkgreen.morphia.query.Query;
import com.github.jmkgreen.morphia.query.QueryException;
import com.github.jmkgreen.morphia.query.QueryImpl;
import com.github.jmkgreen.morphia.query.UpdateException;
import com.github.jmkgreen.morphia.query.UpdateOperations;
import com.github.jmkgreen.morphia.query.UpdateOpsImpl;
import com.github.jmkgreen.morphia.query.UpdateResults;
import com.github.jmkgreen.morphia.utils.Assert;
import com.github.jmkgreen.morphia.utils.IndexDirection;
import com.github.jmkgreen.morphia.utils.IndexFieldDef;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.CommandResult;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBDecoderFactory;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
import com.mongodb.MapReduceCommand;
import com.mongodb.MapReduceCommand.OutputType;
import com.mongodb.MapReduceOutput;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* A generic (type-safe) wrapper around mongodb collections.
*
* @author Scott Hernandez
*/
@SuppressWarnings({"unchecked", "deprecation"})
public class DatastoreImpl implements Datastore, AdvancedDatastore {
    /**
     *
     */
    private static final Logr log = MorphiaLoggerFactory.get(DatastoreImpl.class);

    /**
     *
     */
    protected Mapper mapr;

    /**
     *
     */
    protected Mongo mongo;

    /**
     *
     */
    protected DB db;

    /**
     *
     */
    protected WriteConcern defConcern = WriteConcern.SAFE;

    /**
     *
     */
    protected DBDecoderFactory decoderFactory = null;

    /**
     * @param mapr
     * @param mongo
     * @param dbName
     */
    public DatastoreImpl(Mapper mapr, Mongo mongo, String dbName) {
        this.mapr = mapr;
        this.mongo = mongo;
        this.db = mongo.getDB(dbName);

        // VERY discussable
        DatastoreHolder.getInstance().set(this);
    }

    /**
     * @param morphia
     * @param mongo
     */
    public DatastoreImpl(Morphia morphia, Mongo mongo) {
        this(morphia, mongo, null);
    }

    /**
     * @param morphia
     * @param mongo
     * @param dbName
     * @param username
     * @param password
     */
    public DatastoreImpl(Morphia morphia, Mongo mongo, String dbName, String username, char[] password) {
        this(morphia.getMapper(), mongo, dbName);

        if (username != null)
            if (!this.db.authenticate(username, password))
                throw new AuthenticationException("User '" + username
                        + "' cannot be authenticated with the given password for database '" + dbName + "'");

    }

    /**
     * @param morphia
     * @param mongo
     * @param dbName
     */
    public DatastoreImpl(Morphia morphia, Mongo mongo, String dbName) {
        this(morphia.getMapper(), mongo, dbName);
    }

    /**
     * @param db
     * @return
     */
    public Datastore copy(String db) {
        return new DatastoreImpl(mapr, mongo, db);
    }

    /**
     * @param clazz
     * @param id
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> DBRef createRef(Class<T> clazz, V id) {
        if (id == null)
            throw new MappingException("Could not get id for " + clazz.getName());
        return new DBRef(getDB(), getCollection(clazz).getName(), id);
    }

    /**
     * @param entity
     * @param <T>
     * @return
     */
    public <T> DBRef createRef(T entity) {
        entity = ProxyHelper.unwrap(entity);
        Object id = mapr.getId(entity);
        if (id == null)
            throw new MappingException("Could not get id for " + entity.getClass().getName());
        return createRef(entity.getClass(), id);
    }

    /**
     * @param kind
     * @param id
     * @param <T>
     * @return
     */
    public <T> WriteResult delete(String kind, T id) {
        DBCollection dbColl = getCollection(kind);
        WriteResult wr = dbColl.remove(BasicDBObjectBuilder.start().add(Mapper.ID_KEY, id).get());
        throwOnError(null, wr);
        return wr;
    }

    /**
     * @param kind
     * @param clazz
     * @param id
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> WriteResult delete(String kind, Class<T> clazz, V id) {
        return delete(find(kind, clazz).filter(Mapper.ID_KEY, id));
    }

    /**
     * @param clazz
     * @param id
     * @param wc
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> WriteResult delete(Class<T> clazz, V id, WriteConcern wc) {
        return delete(createQuery(clazz).filter(Mapper.ID_KEY, id), wc);
    }

    /**
     * @param clazz
     * @param id
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> WriteResult delete(Class<T> clazz, V id) {
        return delete(clazz, id, getWriteConcern(clazz));
    }

    /**
     * @param clazz
     * @param ids
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> WriteResult delete(Class<T> clazz, Iterable<V> ids) {
        Query<T> q = find(clazz).disableValidation().filter(Mapper.ID_KEY + " in", ids);
        return delete(q);
    }

    /**
     * @param entity
     * @param <T>
     * @return
     */
    public <T> WriteResult delete(T entity) {
        return delete(entity, getWriteConcern(entity));
    }

    /**
     * @param entity
     * @param wc
     * @param <T>
     * @return
     */
    public <T> WriteResult delete(T entity, WriteConcern wc) {
        entity = ProxyHelper.unwrap(entity);
        if (entity instanceof Class<?>)
            throw new MappingException("Did you mean to delete all documents? -- delete(ds.createQuery(???.class))");
        try {
            Object id = mapr.getId(entity);
            return delete(entity.getClass(), id, wc);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param query
     * @param <T>
     * @return
     */
    public <T> WriteResult delete(Query<T> query) {
        return delete(query, getWriteConcern(query.getEntityClass()));
    }

    /**
     * @param query
     * @param wc
     * @param <T>
     * @return
     */
    public <T> WriteResult delete(Query<T> query, WriteConcern wc) {
        DBCollection dbColl = query.getCollection();
        //TODO remove this after testing.
        if (dbColl == null)
            dbColl = getCollection(query.getEntityClass());

        WriteResult wr;

        if (query.getSortObject() != null || query.getOffset() != 0 || query.getLimit() > 0)
            throw new QueryException("Delete does not allow sort/offset/limit query options.");

        if (query.getQueryObject() != null)
            if (wc == null)
                wr = dbColl.remove(query.getQueryObject());
            else
                wr = dbColl.remove(query.getQueryObject(), wc);
        else if (wc == null)
            wr = dbColl.remove(new BasicDBObject());
        else
            wr = dbColl.remove(new BasicDBObject(), wc);

        throwOnError(wc, wr);

        return wr;
    }

    /**
     * @param type
     * @param fields
     * @param <T>
     */
    public <T> void ensureIndex(Class<T> type, String fields) {
        ensureIndex(type, null, fields, false, false);
    }

    /**
     * @param clazz
     * @param name
     * @param fields
     * @param unique
     * @param dropDupsOnCreate
     * @param <T>
     */
    public <T> void ensureIndex(Class<T> clazz, String name, String fields, boolean unique, boolean dropDupsOnCreate) {
        ensureIndex(clazz, name, QueryImpl.parseFieldsString(fields, clazz, mapr, true), unique, dropDupsOnCreate, false, false, -1);
    }

    /**
     * @param clazz
     * @param name
     * @param fields
     * @param unique
     * @param dropDupsOnCreate
     * @param background
     * @param <T>
     */
    public <T> void ensureIndex(Class<T> clazz, String name, String fields, boolean unique, boolean dropDupsOnCreate, boolean background) {
        ensureIndex(clazz, name, QueryImpl.parseFieldsString(fields, clazz, mapr, true), unique, dropDupsOnCreate, background, false, -1);
    }

    /**
     * @param clazz
     * @param name
     * @param defs
     * @param unique
     * @param dropDupsOnCreate
     * @param <T>
     */
    public <T> void ensureIndex(Class<T> clazz, String name, IndexFieldDef[] defs, boolean unique, boolean dropDupsOnCreate) {
        ensureIndex(clazz, name, defs, unique, dropDupsOnCreate, false);
    }

    /**
     * @param clazz
     * @param name
     * @param defs
     * @param unique
     * @param dropDupsOnCreate
     * @param background
     */
    @SuppressWarnings({"rawtypes"})
    public void ensureIndex(Class clazz, String name, IndexFieldDef[] defs, boolean unique, boolean dropDupsOnCreate, boolean background) {
        BasicDBObjectBuilder keys = BasicDBObjectBuilder.start();

        for (IndexFieldDef def : defs) {
            String fieldName = def.getField();
            IndexDirection dir = def.getDirection();
            keys.add(fieldName, dir.toIndexValue());
        }

        ensureIndex(clazz, name, (BasicDBObject) keys.get(), unique, dropDupsOnCreate, background, false, -1);
    }

    /**
     * @param type
     * @param name
     * @param dir
     * @param <T>
     */
    public <T> void ensureIndex(Class<T> type, String name, IndexDirection dir) {
        ensureIndex(type, new IndexFieldDef(name, dir));
    }

    /**
     * @param type
     * @param fields
     * @param <T>
     */
    public <T> void ensureIndex(Class<T> type, IndexFieldDef... fields) {
        ensureIndex(type, null, fields, false, false);
    }

    /**
     * @param type
     * @param background
     * @param fields
     * @param <T>
     */
    public <T> void ensureIndex(Class<T> type, boolean background, IndexFieldDef... fields) {
        ensureIndex(type, null, fields, false, false, background);
    }

    /**
     * This method actually adds indexes to the selected collection.
     *
     * @param clazz The class/collection to index
     * @param name Optional index name
     * @param fields
     * @param unique
     * @param dropDupsOnCreate
     * @param background
     * @param sparse
     */
    protected <T> void ensureIndex(Class<T> clazz, String name, BasicDBObject fields, boolean unique, boolean dropDupsOnCreate, boolean background, boolean sparse, int expireAfterSeconds) {
        BasicDBObjectBuilder keyOpts = new BasicDBObjectBuilder();
        if (name != null && name.length() > 0) {
            keyOpts.add("name", name);
        }
        if (unique) {
            keyOpts.add("unique", true);
            if (dropDupsOnCreate)
                keyOpts.add("dropDups", true);
        }

        if (background)
            keyOpts.add("background", true);
        if (sparse)
            keyOpts.add("sparse", true);
        if (expireAfterSeconds > -1) {
            keyOpts.add("expireAfterSeconds", expireAfterSeconds);
        }

        DBCollection dbColl = getCollection(clazz);

        BasicDBObject opts = (BasicDBObject) keyOpts.get();
        if (opts.isEmpty()) {
            log.debug("Ensuring index for " + dbColl.getName() + " with keys:" + fields);
            dbColl.ensureIndex(fields);
        } else {
            log.debug("Ensuring index for " + dbColl.getName() + " with keys:" + fields + " and opts:" + opts);
            dbColl.ensureIndex(fields, opts);
        }
    }

    /**
     * Causes a (foreground, blocking) index creation across mapped classes.
     * A proxy to {@link #ensureIndexes(boolean)} passing false in.
     */
    public void ensureIndexes() {
        ensureIndexes(false);
    }

    /**
     * Proxy to {@link #ensureIndexes(Class, boolean)} running the task in the foreground.
     *
     * @param clazz The class to index.
     */
    public <T> void ensureIndexes(Class<T> clazz) {
        ensureIndexes(clazz, false);
    }

    /**
     * @param clazz
     * @param background
     * @param <T>
     */
    public <T> void ensureIndexes(Class<T> clazz, boolean background) {
        MappedClass mc = mapr.getMappedClass(clazz);
        ensureIndexes(mc, background);
    }

    /**
     * Proxy to {@link #ensureIndexes(MappedClass, boolean)} for each mapped class.
     *
     * @param background If true, run the indexing in the background; otherwise it's a foreground task.
     */
    public void ensureIndexes(boolean background) {
        for (MappedClass mc : mapr.getMappedClasses()) {
            ensureIndexes(mc, background);
        }
    }

    /**
     * Runs the indexing process for the given mapped class.
     *
     * @param mc The mapped class to index
     * @param background In the background?
     */
    protected void ensureIndexes(MappedClass mc, boolean background) {
        ensureIndexes(mc, background, new ArrayList<MappedClass>(), new ArrayList<MappedField>());
    }

    /**
     * @param mc
     * @param background
     * @param parentMCs
     * @param parentMFs
     */
    protected void ensureIndexes(MappedClass mc, boolean background, ArrayList<MappedClass> parentMCs, ArrayList<MappedField> parentMFs) {
        if (parentMCs.contains(mc))
            return;

        //skip embedded types
        if (mc.getEmbeddedAnnotation() != null && parentMCs.isEmpty())
            return;

        ensureIndexesFromClassAnnotation(mc, background);

        ensureIndexesFromFieldsAndEmbeddedEntities(mc, background, parentMCs, parentMFs);
    }

    /**
     * Ensure indexes from class annotation
     * @param mc
     * @param background
     */
    private void ensureIndexesFromClassAnnotation(MappedClass mc, boolean background) {
        ArrayList<Annotation> idxs = mc.getAnnotations(Indexes.class);
        if (idxs != null)
            for (Annotation ann : idxs) {
                Indexes idx = (Indexes) ann;
                if (idx != null && idx.value() != null && idx.value().length > 0)
                    for (Index index : idx.value()) {
                        BasicDBObject fields = QueryImpl.parseFieldsString(index.value(), mc.getClazz(), mapr, !index.disableValidation());
                        ensureIndex(mc.getClazz(), index.name(), fields, index.unique(), index.dropDups(), index.background() ? index.background() : background, index.sparse() ? index.sparse() : false, index.expireAfterSeconds());
                    }
            }
    }

    /**
     * Ensure indexes from field annotations, and embedded entities.
     * @param mc
     * @param background
     * @param parentMCs
     * @param parentMFs
     */
    private void ensureIndexesFromFieldsAndEmbeddedEntities(MappedClass mc, boolean background, ArrayList<MappedClass> parentMCs, ArrayList<MappedField> parentMFs) {
        for (MappedField mf : mc.getMappedFields()) {
            Class<?> indexedClass = (parentMCs.isEmpty() ? mc : parentMCs.get(0)).getClazz();
            String field = getFullFieldName(parentMCs, parentMFs, mf);
            if (mf.hasAnnotation(Indexed.class)) {
                Indexed index = mf.getAnnotation(Indexed.class);

                ensureIndex(indexedClass, index.name(), new BasicDBObject(field.toString(), index.value().toIndexValue()), index.unique(), index.dropDups(), index.background() ? index.background() : background, index.sparse() ? index.sparse() : false, index.expireAfterSeconds());
            }

            if (mf.hasAnnotation(SimpleTextIndex.class)) {
                createTextIndex(mf, indexedClass, field);
            }

            if (!mf.isTypeMongoCompatible() && !mf.hasAnnotation(Reference.class) && !mf.hasAnnotation(Serialized.class)) {
                ArrayList<MappedClass> newParentClasses = (ArrayList<MappedClass>) parentMCs.clone();
                ArrayList<MappedField> newParents = (ArrayList<MappedField>) parentMFs.clone();
                newParentClasses.add(mc);
                newParents.add(mf);
                ensureIndexes(mapr.getMappedClass(mf.isSingleValue() ? mf.getType() : mf.getSubClass()), background, newParentClasses, newParents);
            }

        }
    }

    private void createTextIndex(MappedField mf, Class<?> indexedClass, String field) {
        SimpleTextIndex txtIndex = mf.getAnnotation(SimpleTextIndex.class);
        TextIndexCommand cmd = new TextIndexCommand();
        cmd.setName(field);
        if (!txtIndex.name().isEmpty()) {
            cmd.setName(txtIndex.name());
        }
        cmd.setDefaultLanguage(txtIndex.defaultLanguage());
        List<String> fields = new ArrayList();
        fields.add(field);
        cmd.setFields(fields);
        createTextIndex(indexedClass, cmd);
    }

    private void createTextIndex(Class<?> indexedClass, TextIndexCommand cmd) {
        DBCollection col = getCollection(indexedClass);
        col.ensureIndex(cmd.getKeys(), cmd.getOptions());
    }

    private String getFullFieldName(ArrayList<MappedClass> parentMCs, ArrayList<MappedField> parentMFs, MappedField mf) {
        StringBuilder field = new StringBuilder();
        if (!parentMCs.isEmpty())
            for (MappedField pmf : parentMFs)
                field.append(pmf.getNameToStore()).append(".");

        field.append(mf.getNameToStore());
        return field.toString();
    }

    /**
     *
     */
    public void ensureCaps() {
        for (MappedClass mc : mapr.getMappedClasses()) {
            CappedAt cap = mc.getCappedAt();
            if (cap != null && cap.value() > 0) {
                String collName = mapr.getCollectionName(mc.getClazz());
                BasicDBObjectBuilder dbCapOpts = BasicDBObjectBuilder.start("capped", true);
                if (cap.value() > 0)
                    dbCapOpts.add("size", cap.value());
                if (cap.count() > 0)
                    dbCapOpts.add("max", cap.count());
                DB db = getDB();
                if (db.getCollectionNames().contains(collName)) {
                    DBObject dbResult = db.command(BasicDBObjectBuilder.start("collstats", collName).get());
                    if (dbResult.containsField("capped")) {
                        // TODO: check the cap options.
                        log.warning("DBCollection already exists is cap'd already; doing nothing. " + dbResult);
                    } else {
                        log.warning("DBCollection already exists with same name(" + collName
                                + ") and is not cap'd; not creating cap'd version!");
                    }
                } else {
                    getDB().createCollection(collName, dbCapOpts.get());
                    log.debug("Created cap'd DBCollection (" + collName + ") with opts " + dbCapOpts);
                }
            }
        }
    }

    /**
     * @param ex
     * @param <T>
     * @return
     */
    public <T> Query<T> queryByExample(T ex) {
        return queryByExample(getCollection(ex), ex);
    }

    /**
     * @param kind
     * @param ex
     * @param <T>
     * @return
     */
    public <T> Query<T> queryByExample(String kind, T ex) {
        return queryByExample(db.getCollection(kind), ex);
    }

    /**
     * @param coll
     * @param example
     * @param <T>
     * @return
     */
    private <T> Query<T> queryByExample(DBCollection coll, T example) {
        //TODO: think about remove className from baseQuery param below.
        return new QueryImpl<T>((Class<T>) example.getClass(), coll, this, entityToDBObj(example, new HashMap<Object, DBObject>()));

    }

    /**
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Query<T> createQuery(Class<T> clazz) {
        return new QueryImpl<T>(clazz, getCollection(clazz), this);
    }

    /**
     * @param kind
     * @param q
     * @param <T>
     * @return
     */
    public <T> Query<T> createQuery(Class<T> kind, DBObject q) {
        return new QueryImpl<T>(kind, getCollection(kind), this, q);
    }

    /**
     * @param kind
     * @param clazz
     * @param q
     * @param <T>
     * @return
     */
    public <T> Query<T> createQuery(String kind, Class<T> clazz, DBObject q) {
        return new QueryImpl<T>(clazz, db.getCollection(kind), this, q);
    }

    /**
     * @param kind
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Query<T> createQuery(String kind, Class<T> clazz) {
        return new QueryImpl<T>(clazz, db.getCollection(kind), this);
    }

    /**
     * @param kind
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Query<T> find(String kind, Class<T> clazz) {
        return new QueryImpl<T>(clazz, getCollection(kind), this);
    }

    /**
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Query<T> find(Class<T> clazz) {
        return createQuery(clazz);
    }

    /**
     * @param clazz
     * @param property
     * @param value
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> Query<T> find(Class<T> clazz, String property, V value) {
        Query<T> query = createQuery(clazz);
        return query.filter(property, value);
    }

    /**
     * @param kind
     * @param clazz
     * @param property
     * @param value
     * @param offset
     * @param size
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> Query<T> find(String kind, Class<T> clazz, String property, V value, int offset, int size) {
        return find(kind, clazz, property, value, offset, size, true);
    }

    /**
     * @param kind
     * @param clazz
     * @param property
     * @param value
     * @param offset
     * @param size
     * @param validate
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> Query<T> find(String kind, Class<T> clazz, String property, V value, int offset, int size,
                                boolean validate) {
        Query<T> query = find(kind, clazz);
        if (!validate)
            query.disableValidation();
        query.offset(offset);
        query.limit(size);
        return query.filter(property, value).enableValidation();
    }

    /**
     * @param clazz
     * @param property
     * @param value
     * @param offset
     * @param size
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> Query<T> find(Class<T> clazz, String property, V value, int offset, int size) {
        Query<T> query = createQuery(clazz);
        query.offset(offset);
        query.limit(size);
        return query.filter(property, value);
    }

    /**
     * @param clazz
     * @param ref
     * @param <T>
     * @return
     */
    public <T> T get(Class<T> clazz, DBRef ref) {
        return (T) mapr.fromDBObject(clazz, ref.fetch(), createCache());
    }

    /**
     * @param clazz
     * @param ids
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> Query<T> get(Class<T> clazz, Iterable<V> ids) {
        return find(clazz).disableValidation().filter(Mapper.ID_KEY + " in", ids).enableValidation();
    }

    /**
     * Queries the server to check for each DBRef.
     */
    public <T> List<Key<T>> getKeysByRefs(List<DBRef> refs) {
        ArrayList<Key<T>> tempKeys = new ArrayList<Key<T>>(refs.size());

        Map<String, List<DBRef>> kindMap = new HashMap<String, List<DBRef>>();
        for (DBRef ref : refs) {
            if (kindMap.containsKey(ref.getRef()))
                kindMap.get(ref.getRef()).add(ref);
            else
                kindMap.put(ref.getRef(), new ArrayList<DBRef>(Collections.singletonList((DBRef) ref)));
        }
        for (String kind : kindMap.keySet()) {
            List<Object> objIds = new ArrayList<Object>();
            List<DBRef> kindRefs = kindMap.get(kind);
            for (DBRef key : kindRefs) {
                objIds.add(key.getId());
            }
            List<Key<T>> kindResults = this.<T>find(kind, null).disableValidation().filter("_id in", objIds).asKeyList();
            tempKeys.addAll(kindResults);
        }

        //put them back in order, minus the missing ones.
        ArrayList<Key<T>> keys = new ArrayList<Key<T>>(refs.size());
        for (DBRef ref : refs) {
            Key<T> testKey = mapr.refToKey(ref);
            if (tempKeys.contains(testKey))
                keys.add(testKey);
        }
        return keys;
    }

    /**
     * @param keys
     * @param <T>
     * @return
     */
    public <T> List<T> getByKeys(Iterable<Key<T>> keys) {
        return this.getByKeys((Class<T>) null, keys);
    }

    /**
     * @param clazz
     * @param keys
     * @param <T>
     * @return
     */
    @SuppressWarnings("rawtypes")
    public <T> List<T> getByKeys(Class<T> clazz, Iterable<Key<T>> keys) {

        Map<String, List<Key>> kindMap = new HashMap<String, List<Key>>();
        List<T> entities = new ArrayList<T>();
        String clazzKind = (clazz == null) ? null :
                getMapper().getCollectionName(clazz);
        for (Key<?> key : keys) {
            mapr.updateKind(key);

            if (clazzKind != null && !key.getKind().equals(clazzKind))
                throw new IllegalArgumentException("Types are not equal (" +
                        clazz + "!=" + key.getKindClass() +
                        ") for key and method parameter clazz");

            if (kindMap.containsKey(key.getKind()))
                kindMap.get(key.getKind()).add(key);
            else
                kindMap.put(key.getKind(), new ArrayList<Key>(Collections.singletonList((Key) key)));
        }
        for (String kind : kindMap.keySet()) {
            List<Object> objIds = new ArrayList<Object>();
            List<Key> kindKeys = kindMap.get(kind);
            Class<T> kindClass = clazz == null ? kindKeys.get(0).getKindClass() : clazz;
            for (Key key : kindKeys) {
                objIds.add(key.getId());
            }
            List kindResults = find(kind, kindClass).disableValidation().filter("_id in", objIds).asList();
            entities.addAll(kindResults);
        }

        //TODO: order them based on the incoming Keys.
        return entities;
    }

    /**
     * @param kind
     * @param clazz
     * @param id
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> T get(String kind, Class<T> clazz, V id) {
        List<T> results = find(kind, clazz, Mapper.ID_KEY, id, 0, 1).asList();
        if (results == null || results.size() == 0)
            return null;
        return results.get(0);
    }

    /**
     * @param clazz
     * @param id
     * @param <T>
     * @param <V>
     * @return
     */
    public <T, V> T get(Class<T> clazz, V id) {
        return find(getCollection(clazz).getName(), clazz, Mapper.ID_KEY, id, 0, 1, true).get();
    }

    /**
     * @param clazz
     * @param key
     * @param <T>
     * @return
     */
    public <T> T getByKey(Class<T> clazz, Key<T> key) {
        String kind = mapr.getCollectionName(clazz);
        String keyKind = mapr.updateKind(key);
        if (!kind.equals(keyKind))
            throw new RuntimeException("collection names don't match for key and class: " + kind + " != " + keyKind);

        return get(clazz, key.getId());
    }

    /**
     * @param entity
     * @param <T>
     * @return
     */
    public <T> T get(T entity) {
        entity = ProxyHelper.unwrap(entity);
        Object id = mapr.getId(entity);
        if (id == null)
            throw new MappingException("Could not get id for " + entity.getClass().getName());
        return (T) get(entity.getClass(), id);
    }

    /**
     * @param entityOrKey
     * @return
     */
    public Key<?> exists(Object entityOrKey) {
        entityOrKey = ProxyHelper.unwrap(entityOrKey);
        Key<?> key = mapr.getKey(entityOrKey);
        Object id = key.getId();
        if (id == null)
            throw new MappingException("Could not get id for " + entityOrKey.getClass().getName());

        String collName = key.getKind();
        if (collName == null)
            collName = getCollection(key.getKindClass()).getName();

        return find(collName, key.getKindClass()).filter(Mapper.ID_KEY, key.getId()).getKey();
    }

    /**
     * @param clazz
     * @return
     */
    @SuppressWarnings("rawtypes")
    public DBCollection getCollection(Class clazz) {
        String collName = mapr.getCollectionName(clazz);
        DBCollection dbC = getDB().getCollection(collName);
        return dbC;
    }

    /**
     * @param obj
     * @return
     */
    public DBCollection getCollection(Object obj) {
        if (obj == null) return null;
        return getCollection(obj.getClass());
    }

    /**
     * @param kind
     * @return
     */
    protected DBCollection getCollection(String kind) {
        if (kind == null) return null;
        return getDB().getCollection(kind);
    }

    /**
     * @param entity
     * @param <T>
     * @return
     */
    public <T> long getCount(T entity) {
        entity = ProxyHelper.unwrap(entity);
        return getCollection(entity).count();
    }

    /**
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> long getCount(Class<T> clazz) {
        return getCollection(clazz).count();
    }

    /**
     * @param kind
     * @return
     */
    public long getCount(String kind) {
        return getCollection(kind).count();
    }

    /**
     * @param query
     * @param <T>
     * @return
     */
    public <T> long getCount(Query<T> query) {
        return query.countAll();
    }

    /**
     * @return
     */
    public Mongo getMongo() {
        return this.mongo;
    }

    /**
     * @return
     */
    public DB getDB() {
        return db;
    }

    /**
     * @return
     */
    public Mapper getMapper() {
        return mapr;
    }

    /**
     * @param entities
     * @param <T>
     * @return
     */
    public <T> Iterable<Key<T>> insert(Iterable<T> entities) {
        //TODO: try not to create two iterators...
        Object first = entities.iterator().next();
        return insert(entities, getWriteConcern(first));
    }

    /**
     * @param kind
     * @param entities
     * @param wc
     * @param <T>
     * @return
     */
    public <T> Iterable<Key<T>> insert(String kind, Iterable<T> entities, WriteConcern wc) {
        DBCollection dbColl = db.getCollection(kind);
        return insert(dbColl, entities, wc);
    }

    /**
     * @param kind
     * @param entities
     * @param <T>
     * @return
     */
    public <T> Iterable<Key<T>> insert(String kind, Iterable<T> entities) {
        return insert(kind, entities, getWriteConcern(entities.iterator().next()));
    }

    /**
     * @param entities
     * @param wc
     * @param <T>
     * @return
     */
    public <T> Iterable<Key<T>> insert(Iterable<T> entities, WriteConcern wc) {
        //TODO: Do this without creating another iterator
        DBCollection dbColl = getCollection(entities.iterator().next());
        return insert(dbColl, entities, wc);
    }

    /**
     * @param dbColl
     * @param entities
     * @param wc
     * @param <T>
     * @return
     */
    private <T> Iterable<Key<T>> insert(DBCollection dbColl, Iterable<T> entities, WriteConcern wc) {
        ArrayList<DBObject> ents = entities instanceof List ? new ArrayList<DBObject>(((List<T>) entities).size()) : new ArrayList<DBObject>();

        Map<Object, DBObject> involvedObjects = new LinkedHashMap<Object, DBObject>();
        for (T ent : entities) {
            MappedClass mc = mapr.getMappedClass(ent);
            if (mc.getAnnotation(NotSaved.class) != null)
                throw new MappingException("Entity type: " + mc.getClazz().getName() + " is marked as NotSaved which means you should not try to save it!");
            ents.add(entityToDBObj(ent, involvedObjects));
        }

        WriteResult wr = null;

        DBObject[] dbObjs = new DBObject[ents.size()];
        dbColl.insert(ents.toArray(dbObjs), wc);

        throwOnError(wc, wr);

        ArrayList<Key<T>> savedKeys = new ArrayList<Key<T>>();
        Iterator<T> entitiesIT = entities.iterator();
        Iterator<DBObject> dbObjsIT = ents.iterator();

        while (entitiesIT.hasNext()) {
            T entity = entitiesIT.next();
            DBObject dbObj = dbObjsIT.next();
            savedKeys.add(postSaveGetKey(entity, dbObj, dbColl, involvedObjects));
        }

        return savedKeys;
    }

    /**
     * @param entities
     * @param <T>
     * @return
     */
    public <T> Iterable<Key<T>> insert(T... entities) {
        return insert(Arrays.asList(entities), getWriteConcern(entities[0]));
    }

    /**
     * @param entity
     * @param <T>
     * @return
     */
    public <T> Key<T> insert(T entity) {
        return insert(entity, getWriteConcern(entity));
    }

    /**
     * @param entity
     * @param wc
     * @param <T>
     * @return
     */
    public <T> Key<T> insert(T entity, WriteConcern wc) {
        entity = ProxyHelper.unwrap(entity);
        DBCollection dbColl = getCollection(entity);
        return insert(dbColl, entity, wc);
    }

    /**
     * @param kind
     * @param entity
     * @param <T>
     * @return
     */
    public <T> Key<T> insert(String kind, T entity) {
        entity = ProxyHelper.unwrap(entity);
        DBCollection dbColl = getCollection(kind);
        return insert(dbColl, entity, getWriteConcern(entity));
    }

    /**
     * @param kind
     * @param entity
     * @param wc
     * @param <T>
     * @return
     */
    public <T> Key<T> insert(String kind, T entity, WriteConcern wc) {
        entity = ProxyHelper.unwrap(entity);
        DBCollection dbColl = getCollection(kind);
        return insert(dbColl, entity, wc);
    }

    /**
     * @param dbColl
     * @param entity
     * @param wc
     * @param <T>
     * @return
     */
    protected <T> Key<T> insert(DBCollection dbColl, T entity, WriteConcern wc) {
        LinkedHashMap<Object, DBObject> involvedObjects = new LinkedHashMap<Object, DBObject>();
        DBObject dbObj = entityToDBObj(entity, involvedObjects);
        WriteResult wr;
        if (wc == null)
            wr = dbColl.insert(dbObj);
        else
            wr = dbColl.insert(dbObj, wc);

        throwOnError(wc, wr);

        return postSaveGetKey(entity, dbObj, dbColl, involvedObjects);

    }

    /**
     * @param entity
     * @param involvedObjects
     * @return
     */
    protected DBObject entityToDBObj(Object entity, Map<Object, DBObject> involvedObjects) {
        entity = ProxyHelper.unwrap(entity);
        DBObject dbObj = mapr.toDBObject(entity, involvedObjects);
        return dbObj;
    }

    /**
     * call postSaveOperations and returns Key for entity.
     */
    protected <T> Key<T> postSaveGetKey(T entity, DBObject dbObj, DBCollection dbColl, Map<Object, DBObject> involvedObjects) {
        if (dbObj.get(Mapper.ID_KEY) == null)
            throw new MappingException("Missing _id after save!");

        postSaveOperations(entity, dbObj, involvedObjects);
        Key<T> key = new Key<T>(dbColl.getName(), mapr.getId(entity));
        key.setKindClass((Class<? extends T>) entity.getClass());

        return key;
    }

    /**
     * @param entities
     * @param <T>
     * @return
     */
    public <T> Iterable<Key<T>> save(Iterable<T> entities) {
        Object first = null;
        try {
            first = entities.iterator().next();
        } catch (Exception e) {
            //do nothing
        }
        return save(entities, getWriteConcern(first));
    }

    /**
     * @param entities
     * @param wc
     * @param <T>
     * @return
     */
    public <T> Iterable<Key<T>> save(Iterable<T> entities, WriteConcern wc) {
        ArrayList<Key<T>> savedKeys = new ArrayList<Key<T>>();
        for (T ent : entities)
            savedKeys.add(save(ent, wc));
        return savedKeys;

    }

    /**
     * @param entities
     * @param <T>
     * @return
     */
    public <T> Iterable<Key<T>> save(T... entities) {
        ArrayList<Key<T>> savedKeys = new ArrayList<Key<T>>();
        for (T ent : entities)
            savedKeys.add(save(ent));
        return savedKeys;
    }

    /**
     * @param dbColl
     * @param entity
     * @param wc
     * @param <T>
     * @return
     */
    protected <T> Key<T> save(DBCollection dbColl, T entity, WriteConcern wc) {
        MappedClass mc = mapr.getMappedClass(entity);
        if (mc.getAnnotation(NotSaved.class) != null)
            throw new MappingException("Entity type: " + mc.getClazz().getName() + " is marked as NotSaved which means you should not try to save it!");

        WriteResult wr = null;

        //involvedObjects is used not only as a cache but also as a list of what needs to be called for life-cycle methods at the end.
        LinkedHashMap<Object, DBObject> involvedObjects = new LinkedHashMap<Object, DBObject>();
        DBObject dbObj = entityToDBObj(entity, involvedObjects);

        //try to do an update if there is a @Version field
        if (mc.hasVersioning()) {
            wr = tryVersionedUpdate(dbColl, entity, dbObj, wc, db, mc);
        } else {
            if (wc == null)
                wr = dbColl.save(dbObj);
            else
                wr = dbColl.save(dbObj, wc);
        }
        throwOnError(wc, wr);
        return postSaveGetKey(entity, dbObj, dbColl, involvedObjects);
    }

    /**
     * @param dbColl
     * @param entity
     * @param dbObj
     * @param wc
     * @param db
     * @param mc
     * @param <T>
     * @return
     */
    protected <T> WriteResult tryVersionedUpdate(DBCollection dbColl, T entity, DBObject dbObj, WriteConcern wc, DB db, MappedClass mc) {
        WriteResult wr = null;

        MappedField mfVersion = mc.getFieldsAnnotatedWith(Version.class).get(0);
        String versionKeyName = mfVersion.getNameToStore();
        Long oldVersion = (Long) mfVersion.getFieldValue(entity);
        long newVersion = VersionHelper.nextValue(oldVersion);
        dbObj.put(versionKeyName, newVersion);

        if (oldVersion != null && oldVersion > 0) {
            Object idValue = dbObj.get(Mapper.ID_KEY);

            UpdateResults<T> res = update(find(dbColl.getName(), (Class<T>) entity.getClass()).filter(Mapper.ID_KEY, idValue).filter(versionKeyName, oldVersion),
                    dbObj,
                    false,
                    false,
                    wc);

            wr = res.getWriteResult();

            if (res.getUpdatedCount() != 1)
                throw new ConcurrentModificationException("Entity of class " + entity.getClass().getName()
                        + " (id='" + idValue + "',version='" + oldVersion + "') was concurrently updated.");
        } else if (wc == null)
            wr = dbColl.save(dbObj);
        else
            wr = dbColl.save(dbObj, wc);

        //update the version.
        mfVersion.setFieldValue(entity, newVersion);
        return wr;
    }

    /**
     * @param wc
     * @param wr
     */
    protected void throwOnError(WriteConcern wc, WriteResult wr) {
        if (wc == null && wr.getLastConcern() == null) {
            CommandResult cr = wr.getLastError();
            if (cr != null && cr.getErrorMessage() != null && cr.getErrorMessage().length() > 0)
                cr.throwOnError();
        }
    }

    /**
     * @param kind
     * @param entity
     * @param <T>
     * @return
     */
    public <T> Key<T> save(String kind, T entity) {
        entity = ProxyHelper.unwrap(entity);
        DBCollection dbColl = getCollection(kind);
        return save(dbColl, entity, getWriteConcern(entity));
    }

    /**
     * @param entity
     * @param <T>
     * @return
     */
    public <T> Key<T> save(T entity) {
        return save(entity, getWriteConcern(entity));
    }

    /**
     * @param entity
     * @param wc
     * @param <T>
     * @return
     */
    public <T> Key<T> save(T entity, WriteConcern wc) {
        entity = ProxyHelper.unwrap(entity);
        DBCollection dbColl = getCollection(entity);
        return save(dbColl, entity, wc);
    }

    public <T> Key<T> save(T entity, DBCollection collection) {
        entity = ProxyHelper.unwrap(entity);
        return save(collection, entity, getWriteConcern(entity));
    }

    public <T> Key<T> save(T entity, DBCollection collection, WriteConcern writeConcern) {
        entity = ProxyHelper.unwrap(entity);
        return save(collection, entity, writeConcern);
    }

    /**
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> UpdateOperations<T> createUpdateOperations(Class<T> clazz) {
        return new UpdateOpsImpl<T>(clazz, getMapper());
    }

    /**
     * @param kind
     * @param ops
     * @param <T>
     * @return
     */
    public <T> UpdateOperations<T> createUpdateOperations(Class<T> kind, DBObject ops) {
        UpdateOpsImpl<T> upOps = (UpdateOpsImpl<T>) createUpdateOperations(kind);
        upOps.setOps(ops);
        return upOps;
    }

    /**
     * @param query
     * @param ops
     * @param createIfMissing
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> update(Query<T> query, UpdateOperations<T> ops, boolean createIfMissing) {
        return update(query, ops, createIfMissing, getWriteConcern(query.getEntityClass()));
    }

    /**
     * @param query
     * @param ops
     * @param createIfMissing
     * @param wc
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> update(Query<T> query, UpdateOperations<T> ops, boolean createIfMissing, WriteConcern wc) {
        return update(query, ops, createIfMissing, true, wc);
    }

    /**
     * @param ent
     * @param ops
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> update(T ent, UpdateOperations<T> ops) {
        if (ent instanceof Query)
            return update((Query<T>) ent, ops);

        MappedClass mc = mapr.getMappedClass(ent);
        Query<T> q = (Query<T>) createQuery(mc.getClazz());
        q.disableValidation().filter(Mapper.ID_KEY, mapr.getId(ent));

        if (mc.getFieldsAnnotatedWith(Version.class).size() > 0) {
            MappedField versionMF = mc.getFieldsAnnotatedWith(Version.class).get(0);
            Long oldVer = (Long) versionMF.getFieldValue(ent);
            q.filter(versionMF.getNameToStore(), oldVer);
            ops.set(versionMF.getNameToStore(), VersionHelper.nextValue(oldVer));
        }

        return update(q, ops);
    }

    /**
     * @param key
     * @param ops
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> update(Key<T> key, UpdateOperations<T> ops) {
        Class<T> clazz = (Class<T>) key.getKindClass();
        if (clazz == null)
            clazz = (Class<T>) mapr.getClassFromKind(key.getKind());
        return updateFirst(createQuery(clazz).disableValidation().filter(Mapper.ID_KEY, key.getId()), ops);
    }

    /**
     * @param query
     * @param ops
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> update(Query<T> query, UpdateOperations<T> ops) {
        return update(query, ops, false, true);
    }

    /**
     * @param query
     * @param ops
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> updateFirst(Query<T> query, UpdateOperations<T> ops) {
        return update(query, ops, false, false);
    }

    /**
     * @param query
     * @param ops
     * @param createIfMissing
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> updateFirst(Query<T> query, UpdateOperations<T> ops, boolean createIfMissing) {
        return update(query, ops, createIfMissing, getWriteConcern(query.getEntityClass()));

    }

    /**
     * @param query
     * @param ops
     * @param createIfMissing
     * @param wc
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> updateFirst(Query<T> query, UpdateOperations<T> ops, boolean createIfMissing, WriteConcern wc) {
        return update(query, ops, createIfMissing, false, wc);
    }

    /**
     * @param query
     * @param entity
     * @param createIfMissing
     * @param <T>
     * @return
     */
    public <T> UpdateResults<T> updateFirst(Query<T> query, T entity, boolean createIfMissing) {
        LinkedHashMap<Object, DBObject> involvedObjects = new LinkedHashMap<Object, DBObject>();
        DBObject dbObj = mapr.toDBObject(entity, involvedObjects);

        UpdateResults<T> res = update(query, dbObj, createIfMissing, false, getWriteConcern(entity));

        //update _id field
        CommandResult gle = res.getWriteResult().getCachedLastError();
        if (gle != null && res.getInsertedCount() > 0)
            dbObj.put(Mapper.ID_KEY, res.getNewId());

        postSaveOperations(entity, dbObj, involvedObjects);
        return res;
    }

    /**
     * @param entity
     * @param <T>
     * @return
     */
    public <T> Key<T> merge(T entity) {
        return merge(entity, getWriteConcern(entity));
    }

    /**
     * @param entity
     * @param wc
     * @param <T>
     * @return
     */
    public <T> Key<T> merge(T entity, WriteConcern wc) {
        LinkedHashMap<Object, DBObject> involvedObjects = new LinkedHashMap<Object, DBObject>();
        DBObject dbObj = mapr.toDBObject(entity, involvedObjects);
        Key<T> key = mapr.getKey(entity);
        entity = ProxyHelper.unwrap(entity);
        Object id = mapr.getId(entity);
        if (id == null)
            throw new MappingException("Could not get id for " + entity.getClass().getName());

        //remove (immutable) _id field for update.
        dbObj.removeField(Mapper.ID_KEY);

        WriteResult wr = null;

        MappedClass mc = mapr.getMappedClass(entity);
        DBCollection dbColl = getCollection(entity);

        //try to do an update if there is a @Version field
        if (mc.hasVersioning()) {
            wr = tryVersionedUpdate(dbColl, entity, dbObj, wc, db, mc);
        } else {
            Query<T> query = (Query<T>) createQuery(entity.getClass()).filter(Mapper.ID_KEY, id);
            wr = update(query, new BasicDBObject("$set", dbObj), false, false, wc).getWriteResult();
        }

        UpdateResults<T> res = new UpdateResults<T>(wr);

        throwOnError(wc, wr);

        //check for updated count if we have a gle
        CommandResult gle = wr.getCachedLastError();
        if (gle != null && res.getUpdatedCount() == 0)
            throw new UpdateException("Not updated: " + gle);

        postSaveOperations(entity, dbObj, involvedObjects);
        return key;
    }

    /**
     * @param entity
     * @param dbObj
     * @param involvedObjects
     * @param <T>
     */
    private <T> void postSaveOperations(Object entity, DBObject dbObj, Map<Object, DBObject> involvedObjects) {
        mapr.updateKeyInfo(entity, dbObj, createCache());

        //call PostPersist on all involved entities (including the entity)
        for (Map.Entry<Object, DBObject> e : involvedObjects.entrySet()) {
            Object ent = e.getKey();
            DBObject dbO = e.getValue();
            MappedClass mc = mapr.getMappedClass(ent);
            mc.callLifecycleMethods(PostPersist.class, ent, dbO, mapr);
        }
    }

    /**
     * @param query
     * @param ops
     * @param createIfMissing
     * @param multi
     * @param wc
     * @param <T>
     * @return
     */
    @SuppressWarnings("rawtypes")
    private <T> UpdateResults<T> update(Query<T> query, UpdateOperations ops, boolean createIfMissing, boolean multi, WriteConcern wc) {
        DBObject u = ((UpdateOpsImpl) ops).getOps();
        if (((UpdateOpsImpl) ops).isIsolated()) {
            Query<T> q = query.clone();
            q.disableValidation().filter("$atomic", true);
            return update(q, u, createIfMissing, multi, wc);
        }
        return update(query, u, createIfMissing, multi, wc);
    }

    /**
     * @param query
     * @param ops
     * @param createIfMissing
     * @param multi
     * @param <T>
     * @return
     */
    @SuppressWarnings("rawtypes")
    private <T> UpdateResults<T> update(Query<T> query, UpdateOperations ops, boolean createIfMissing, boolean multi) {
        return update(query, ops, createIfMissing, multi, getWriteConcern(query.getEntityClass()));
    }

    /**
     * @param query
     * @param u
     * @param createIfMissing
     * @param multi
     * @param wc
     * @param <T>
     * @return
     */
    private <T> UpdateResults<T> update(Query<T> query, DBObject u, boolean createIfMissing, boolean multi, WriteConcern wc) {
        QueryImpl<T> qi = (QueryImpl<T>) query;

        DBCollection dbColl = qi.getCollection();
        //TODO remove this after testing.
        if (dbColl == null)
            dbColl = getCollection(qi.getEntityClass());

        if (qi.getSortObject() != null && qi.getSortObject().keySet() != null && !qi.getSortObject().keySet().isEmpty())
            throw new QueryException("sorting is not allowed for updates.");
        if (qi.getOffset() > 0)
            throw new QueryException("a query offset is not allowed for updates.");
        if (qi.getLimit() > 0)
            throw new QueryException("a query limit is not allowed for updates.");

        DBObject q = qi.getQueryObject();
        if (q == null)
            q = new BasicDBObject();

        if (log.isTraceEnabled())
            log.trace("Executing update(" + dbColl.getName() + ") for query: " + q + ", ops: " + u + ", multi: " + multi + ", upsert: " + createIfMissing);

        WriteResult wr;
        if (wc == null)
            wr = dbColl.update(q, u, createIfMissing, multi);
        else
            wr = dbColl.update(q, u, createIfMissing, multi, wc);

        throwOnError(wc, wr);

        return new UpdateResults<T>(wr);
    }

    /**
     * @param query
     * @param <T>
     * @return
     */
    public <T> T findAndDelete(Query<T> query) {
        DBCollection dbColl = ((QueryImpl<T>) query).getCollection();
        //TODO remove this after testing.
        if (dbColl == null)
            dbColl = getCollection(((QueryImpl<T>) query).getEntityClass());

        QueryImpl<T> qi = ((QueryImpl<T>) query);
        EntityCache cache = createCache();

        if (log.isTraceEnabled())
            log.trace("Executing findAndModify(" + dbColl.getName() + ") with delete ...");

        DBObject result = dbColl.findAndModify(qi.getQueryObject(), qi.getFieldsObject(), qi.getSortObject(), true, null, false, false);

        if (result != null) {
            T entity = (T) mapr.fromDBObject(qi.getEntityClass(), result, cache);
            return entity;
        }

        return null;
    }

    /**
     * @param q
     * @param ops
     * @param <T>
     * @return
     */
    public <T> T findAndModify(Query<T> q, UpdateOperations<T> ops) {
        return findAndModify(q, ops, false);
    }

    /**
     * @param query
     * @param ops
     * @param oldVersion indicated the old version of the Entity should be returned
     * @param <T>
     * @return
     */
    public <T> T findAndModify(Query<T> query, UpdateOperations<T> ops, boolean oldVersion) {
        return findAndModify(query, ops, oldVersion, false);
    }

    /**
     * @param query
     * @param ops
     * @param oldVersion      indicated the old version of the Entity should be returned
     * @param createIfMissing if the query returns no results, then a new object will be created (sets upsert=true)
     * @param <T>
     * @return
     */
    public <T> T findAndModify(Query<T> query, UpdateOperations<T> ops, boolean oldVersion, boolean createIfMissing) {

        DBCollection dbColl = query.getCollection();
        //TODO remove this after testing.
        if (dbColl == null)
            dbColl = getCollection(query.getEntityClass());

        if (log.isTraceEnabled())
            log.info("Executing findAndModify(" + dbColl.getName() + ") with update ");
        DBObject res = null;
        try {
            res = dbColl.findAndModify(query.getQueryObject(),
                    query.getFieldsObject(),
                    query.getSortObject(),
                    false,
                    ((UpdateOpsImpl<T>) ops).getOps(), !oldVersion,
                    createIfMissing);
        } catch (MongoException e) {
            if (e.getMessage() == null || !e.getMessage().contains("matching"))
                throw e;
        }

        if (res == null)
            return null;
        else
            return (T) mapr.fromDBObject(query.getEntityClass(), res, createCache());
    }

    /**
     * @param type        MapreduceType
     * @param q           The query (only the criteria, limit and sort will be used)
     * @param outputType  The type of resulting data; inline is not working yet
     * @param baseCommand The base command to fill in and send to the server
     * @param <T>
     * @return
     */
    @SuppressWarnings("rawtypes")
    public <T> MapreduceResults<T> mapReduce(MapreduceType type, Query q, Class<T> outputType, MapReduceCommand baseCommand) {

        Assert.parametersNotNull("map", baseCommand.getMap());
        Assert.parameterNotEmpty(baseCommand.getMap(), "map");
        Assert.parametersNotNull("reduce", baseCommand.getReduce());
        Assert.parameterNotEmpty(baseCommand.getMap(), "reduce");


        if (MapreduceType.INLINE.equals(type))
            throw new IllegalArgumentException("Inline map/reduce is not supported.");

        if (q.getOffset() != 0 || q.getFieldsObject() != null)
            throw new QueryException("mapReduce does not allow the offset/retrievedFields query options.");


        OutputType outType = OutputType.REPLACE;
        switch (type) {
            case REDUCE:
                outType = OutputType.REDUCE;
                break;
            case MERGE:
                outType = OutputType.MERGE;
                break;
            case INLINE:
                outType = OutputType.INLINE;
                break;
            default:
                outType = OutputType.REPLACE;
                break;
        }

        DBCollection dbColl = q.getCollection();

        MapReduceCommand cmd = new MapReduceCommand(dbColl, baseCommand.getMap(), baseCommand.getReduce(), baseCommand.getOutputTarget(), outType, q.getQueryObject());
        cmd.setFinalize(baseCommand.getFinalize());
        cmd.setScope(baseCommand.getScope());

        if (q.getLimit() > 0)
            cmd.setLimit(q.getLimit());
        if (q.getSortObject() != null)
            cmd.setSort(q.getSortObject());

        if (log.isTraceEnabled())
            log.info("Executing " + cmd.toString());

        MapReduceOutput mpo = dbColl.mapReduce(baseCommand);
        MapreduceResults mrRes = (MapreduceResults) mapr.fromDBObject(MapreduceResults.class, mpo.getRaw(), createCache());

        QueryImpl baseQ = null;
        if (!MapreduceType.INLINE.equals(type))
            baseQ = new QueryImpl(outputType, db.getCollection(mrRes.getOutputCollectionName()), this);
        //TODO Handle inline case and create an iterator/able.

        mrRes.setBits(type, baseQ);
        return mrRes;

    }

    /**
     * @param type
     * @param query
     * @param map
     * @param reduce
     * @param finalize
     * @param scopeFields
     * @param outputType
     * @param <T>
     * @return
     */
    @SuppressWarnings("rawtypes")
    public <T> MapreduceResults<T> mapReduce(MapreduceType type, Query query, String map, String reduce, String finalize, Map<String, Object> scopeFields, Class<T> outputType) {

        DBCollection dbColl = query.getCollection();

        String outColl = mapr.getCollectionName(outputType);

        OutputType outType = OutputType.REPLACE;
        switch (type) {
            case REDUCE:
                outType = OutputType.REDUCE;
                break;
            case MERGE:
                outType = OutputType.MERGE;
                break;
            case INLINE:
                outType = OutputType.INLINE;
                break;
            default:
                outType = OutputType.REPLACE;
                break;
        }

        MapReduceCommand cmd = new MapReduceCommand(dbColl, map, reduce, outColl, outType, query.getQueryObject());

        if (query.getLimit() > 0)
            cmd.setLimit(query.getLimit());
        if (query.getSortObject() != null)
            cmd.setSort(query.getSortObject());

        if (finalize != null && finalize.length() > 0)
            cmd.setFinalize(finalize);

        if (scopeFields != null && scopeFields.size() > 0)
            cmd.setScope(scopeFields);

        return mapReduce(type, query, outputType, cmd);
    }

    /**
     * Converts a list of keys to refs.
     */
    public static <T> List<DBRef> keysAsRefs(List<Key<T>> keys, Mapper mapr) {
        ArrayList<DBRef> refs = new ArrayList<DBRef>(keys.size());
        for (Key<T> key : keys)
            refs.add(mapr.keyToRef(key));
        return refs;
    }

    /**
     * Converts a list of refs to keys.
     */
    public static <T> List<Key<T>> refsToKeys(Mapper mapr, List<DBRef> refs, Class<T> c) {
        ArrayList<Key<T>> keys = new ArrayList<Key<T>>(refs.size());
        for (DBRef ref : refs) {
            keys.add((Key<T>) mapr.refToKey(ref));
        }
        return keys;
    }

    /**
     * @return
     */
    private EntityCache createCache() {
        return mapr.createEntityCache();
    }

    /**
     * Gets the write concern for entity or returns the default write concern for this datastore.
     */
    public WriteConcern getWriteConcern(Object clazzOrEntity) {
        WriteConcern wc = defConcern;
        if (clazzOrEntity != null) {
            wc = getMapper().getMappedClass(clazzOrEntity).getWriteConcern();
        }

        return wc;
    }

    /**
     * @return
     */
    public WriteConcern getDefaultWriteConcern() {
        return defConcern;
    }

    /**
     * @param wc
     */
    public void setDefaultWriteConcern(WriteConcern wc) {
        defConcern = wc;
    }

    /**
     * @param fact
     * @return
     */
    public DBDecoderFactory setDecoderFact(DBDecoderFactory fact) {
        return decoderFactory = fact;
    }

    /**
     * @return
     */
    public DBDecoderFactory getDecoderFact() {
        return decoderFactory != null ? decoderFactory : mongo.getMongoOptions().dbDecoderFactory;
    }
}
TOP

Related Classes of com.github.jmkgreen.morphia.DatastoreImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.