All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mongodb.morphia.DatastoreImpl Maven / Gradle / Ivy

The newest version!
package org.mongodb.morphia;

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.DefaultDBDecoder;
import com.mongodb.MapReduceCommand;
import com.mongodb.MapReduceCommand.OutputType;
import com.mongodb.MongoClient;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.DBCollectionUpdateOptions;
import com.mongodb.client.model.ValidationOptions;
import org.mongodb.morphia.aggregation.AggregationPipeline;
import org.mongodb.morphia.aggregation.AggregationPipelineImpl;
import org.mongodb.morphia.annotations.CappedAt;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.NotSaved;
import org.mongodb.morphia.annotations.PostPersist;
import org.mongodb.morphia.annotations.Validation;
import org.mongodb.morphia.annotations.Version;
import org.mongodb.morphia.logging.Logger;
import org.mongodb.morphia.logging.MorphiaLoggerFactory;
import org.mongodb.morphia.mapping.MappedClass;
import org.mongodb.morphia.mapping.MappedField;
import org.mongodb.morphia.mapping.Mapper;
import org.mongodb.morphia.mapping.MappingException;
import org.mongodb.morphia.mapping.cache.EntityCache;
import org.mongodb.morphia.mapping.lazy.proxy.ProxyHelper;
import org.mongodb.morphia.query.CountOptions;
import org.mongodb.morphia.query.DefaultQueryFactory;
import org.mongodb.morphia.query.Query;
import org.mongodb.morphia.query.QueryException;
import org.mongodb.morphia.query.QueryFactory;
import org.mongodb.morphia.query.UpdateException;
import org.mongodb.morphia.query.UpdateOperations;
import org.mongodb.morphia.query.UpdateOpsImpl;
import org.mongodb.morphia.query.UpdateResults;
import org.mongodb.morphia.utils.Assert;

import java.util.ArrayList;
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;
import java.util.Map.Entry;

import static com.mongodb.BasicDBObject.parse;
import static com.mongodb.BasicDBObjectBuilder.start;
import static com.mongodb.DBCollection.ID_FIELD_NAME;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

/**
 * A generic (type-safe) wrapper around mongodb collections
 *
 * @deprecated This is an internal implementation of a published API.  No public alternative planned.
 */
@Deprecated
@SuppressWarnings("deprecation")
public class DatastoreImpl implements AdvancedDatastore {
    private static final Logger LOG = MorphiaLoggerFactory.get(DatastoreImpl.class);

    private final Morphia morphia;
    private final MongoClient mongoClient;
    private final MongoDatabase database;
    private final IndexHelper indexHelper;
    private DB db;
    private Mapper mapper;
    private WriteConcern defConcern;
    private DBDecoderFactory decoderFactory;

    private volatile QueryFactory queryFactory = new DefaultQueryFactory();

    /**
     * Create a new DatastoreImpl
     *
     * @param morphia     the Morphia instance
     * @param mongoClient the connection to the MongoDB instance
     * @param dbName      the name of the database for this data store.
     * @deprecated This is not meant to be directly instantiated by end user code.  Use
     * {@link Morphia#createDatastore(MongoClient, Mapper, String)}
     */
    @Deprecated
    public DatastoreImpl(final Morphia morphia, final MongoClient mongoClient, final String dbName) {
        this(morphia, morphia.getMapper(), mongoClient, dbName);
    }

    /**
     * Create a new DatastoreImpl
     *
     * @param morphia     the Morphia instance
     * @param mapper      an initialised Mapper
     * @param mongoClient the connection to the MongoDB instance
     * @param dbName      the name of the database for this data store.
     * @deprecated This is not meant to be directly instantiated by end user code.  Use
     * {@link Morphia#createDatastore(MongoClient, Mapper, String)}
     */
    @Deprecated
    public DatastoreImpl(final Morphia morphia, final Mapper mapper, final MongoClient mongoClient, final String dbName) {
        this(morphia, mapper, mongoClient, mongoClient.getDatabase(dbName));
    }

    private DatastoreImpl(final Morphia morphia, final Mapper mapper, final MongoClient mongoClient, final MongoDatabase database) {
        this.morphia = morphia;
        this.mapper = mapper;
        this.mongoClient = mongoClient;
        this.database = database;
        this.db = mongoClient.getDB(database.getName());
        this.defConcern = mongoClient.getWriteConcern();
        this.indexHelper = new IndexHelper(mapper, database);
    }

    /**
     * Creates a copy of this Datastore and all its configuration but with a new database
     *
     * @param database the new database to use for operations
     * @return the new Datastore instance
     * @deprecated use {@link Morphia#createDatastore(MongoClient, Mapper, String)}
     */
    @Deprecated
    public DatastoreImpl copy(final String database) {
        return new DatastoreImpl(morphia, mapper, mongoClient, database);
    }

    /**
     * @param source the initial type/collection to aggregate against
     * @return a new query bound to the kind (a specific {@link DBCollection})
     */
    @Override
    public AggregationPipeline createAggregation(final Class source) {
        return new AggregationPipelineImpl(this, getCollection(source), source);
    }

    @Override
    public AggregationPipeline createAggregation(final String collection, final Class clazz) {
        return new AggregationPipelineImpl(this, getDB().getCollection(collection), clazz);
    }

    @Override
    public  Query createQuery(final Class collection) {
        return newQuery(collection, getCollection(collection));
    }

    @Override
    public  UpdateOperations createUpdateOperations(final Class clazz) {
        return new UpdateOpsImpl(clazz, getMapper());
    }

    @Override
    public  WriteResult delete(final Query query, final DeleteOptions options) {

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

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

        return dbColl.remove(query.getQueryObject(), enforceWriteConcern(options, query.getEntityClass()).getOptions());
    }

    @Override
    public  WriteResult delete(final Class clazz, final V id) {
        return delete(clazz, id, new DeleteOptions().writeConcern(getWriteConcern(clazz)));
    }

    @Override
    public  WriteResult delete(final Class clazz, final V id, final DeleteOptions options) {
        return delete(createQuery(clazz).filter(Mapper.ID_KEY, id), options);
    }

    @Override
    public  WriteResult delete(final Class clazz, final Iterable ids) {
        return delete(find(clazz).filter(Mapper.ID_KEY + " in", ids));
    }

    @Override
    public  WriteResult delete(final Class clazz, final Iterable ids, final DeleteOptions options) {
        return delete(find(clazz).filter(Mapper.ID_KEY + " in", ids), options);
    }

    @Override
    public  WriteResult delete(final Query query) {
        return delete(query, new DeleteOptions().writeConcern(getWriteConcern(query.getEntityClass())));
    }

    @Override
    @Deprecated
    public  WriteResult delete(final Query query, final WriteConcern wc) {
        return delete(query, new DeleteOptions().writeConcern(wc));
    }

    @Override
    public  WriteResult delete(final T entity) {
        return delete(entity, getWriteConcern(entity));
    }

    /**
     * Deletes the given entity (by @Id), with the WriteConcern
     *
     * @param entity  the entity to delete
     * @param options the options to use when deleting
     * @return results of the delete
     */
    @Override
    public  WriteResult delete(final T entity, final DeleteOptions options) {
        final T wrapped = ProxyHelper.unwrap(entity);
        if (wrapped instanceof Class) {
            throw new MappingException("Did you mean to delete all documents? -- delete(ds.createQuery(???.class))");
        }
        try {
            return delete(wrapped.getClass(), mapper.getId(wrapped), options);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @Deprecated
    public  WriteResult delete(final T entity, final WriteConcern wc) {
        return delete(entity, new DeleteOptions().writeConcern(wc));
    }

    @Override
    public void ensureCaps() {
        for (final MappedClass mc : mapper.getMappedClasses()) {
            if (mc.getEntityAnnotation() != null && mc.getEntityAnnotation().cap().value() > 0) {
                final CappedAt cap = mc.getEntityAnnotation().cap();
                final String collName = mapper.getCollectionName(mc.getClazz());
                final BasicDBObjectBuilder dbCapOpts = start("capped", true);
                if (cap.value() > 0) {
                    dbCapOpts.add("size", cap.value());
                }
                if (cap.count() > 0) {
                    dbCapOpts.add("max", cap.count());
                }
                final DB database = getDB();
                if (database.getCollectionNames().contains(collName)) {
                    final DBObject dbResult = database.command(start("collstats", collName).get());
                    if (dbResult.containsField("capped")) {
                        LOG.debug("DBCollection already exists and is capped already; doing nothing. " + dbResult);
                    } else {
                        LOG.warning("DBCollection already exists with same name(" + collName
                                        + ") and is not capped; not creating capped version!");
                    }
                } else {
                    getDB().createCollection(collName, dbCapOpts.get());
                    LOG.debug("Created capped DBCollection (" + collName + ") with opts " + dbCapOpts);
                }
            }
        }
    }

    @Override
    public void enableDocumentValidation() {
        for (final MappedClass mc : mapper.getMappedClasses()) {
            process(mc, (Validation) mc.getAnnotation(Validation.class));
        }
    }

    void process(final MappedClass mc, final Validation validation) {
        if (validation != null) {
            String collectionName = mc.getCollectionName();
            CommandResult result = getDB()
                .command(new BasicDBObject("collMod", collectionName)
                             .append("validator", parse(validation.value()))
                             .append("validationLevel", validation.level().getValue())
                             .append("validationAction", validation.action().getValue())
                        );

            if (!result.ok()) {
                if (result.getInt("code") == 26) {
                    ValidationOptions options = new ValidationOptions()
                        .validator(parse(validation.value()))
                        .validationLevel(validation.level())
                        .validationAction(validation.action());
                    getDatabase().createCollection(collectionName, new CreateCollectionOptions().validationOptions(options));
                } else {
                    result.throwOnError();
                }
            }
        }
    }

    @Override
    public Key exists(final Object entityOrKey) {
        final Query query = buildExistsQuery(entityOrKey);
        return query.getKey();
    }

    @Override
    public  Query find(final Class clazz) {
        return createQuery(clazz);
    }

    @Override
    @Deprecated
    public  Query find(final Class clazz, final String property, final V value) {
        final Query query = createQuery(clazz);
        return query.filter(property, value);
    }

    @Override
    @Deprecated
    public  Query find(final Class clazz, final String property, final V value, final int offset, final int size) {
        final Query query = createQuery(clazz);
        query.offset(offset);
        query.limit(size);
        return query.filter(property, value);
    }

    @Override
    public  T findAndDelete(final Query query) {
        return findAndDelete(query, new FindAndModifyOptions());
    }

    @Override
    public  T findAndDelete(final Query query, final FindAndModifyOptions options) {
        DBCollection dbColl = query.getCollection();
        if (dbColl == null) {
            dbColl = getCollection(query.getEntityClass());
        }

        if (LOG.isTraceEnabled()) {
            LOG.trace("Executing findAndModify(" + dbColl.getName() + ") with delete ...");
        }

        FindAndModifyOptions copy = enforceWriteConcern(options, query.getEntityClass())
            .copy()
            .projection(query.getFieldsObject())
            .sort(query.getSortObject())
            .returnNew(false)
            .upsert(false)
            .remove(true);

        final DBObject result = dbColl.findAndModify(query.getQueryObject(), copy.getOptions());

        return result == null ? null : mapper.fromDBObject(this, query.getEntityClass(), result, createCache());
    }

    @Override
    public  T findAndModify(final Query query, final UpdateOperations operations, final FindAndModifyOptions options) {
        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 ");
        }

        updateForVersioning(query, operations);
        DBObject res = dbColl.findAndModify(query.getQueryObject(), options.copy()
                                                                           .sort(query.getSortObject())
                                                                           .projection(query.getFieldsObject())
                                                                           .update(((UpdateOpsImpl) operations).getOps())
                                           .getOptions());

        return res == null ? null : mapper.fromDBObject(this, query.getEntityClass(), res, createCache());

    }

    @Override
    public  T findAndModify(final Query query, final UpdateOperations operations) {
        return findAndModify(query, operations, new FindAndModifyOptions()
            .returnNew(true));
    }

    @Override
    @Deprecated
    public  T findAndModify(final Query query, final UpdateOperations operations, final boolean oldVersion) {
        return findAndModify(query, operations, new FindAndModifyOptions()
            .returnNew(!oldVersion)
            .upsert(false));
    }

    @Override
    @Deprecated
    public  T findAndModify(final Query query, final UpdateOperations operations, final boolean oldVersion,
                               final boolean createIfMissing) {
        return findAndModify(query, operations, new FindAndModifyOptions()
            .returnNew(!oldVersion)
            .upsert(createIfMissing));

    }

    private  void updateForVersioning(final Query query, final UpdateOperations operations) {
        final MappedClass mc = mapper.getMappedClass(query.getEntityClass());

        if (!mc.getFieldsAnnotatedWith(Version.class).isEmpty()) {
            operations.inc(mc.getMappedVersionField().getNameToStore());
        }

    }

    @Override
    public  Query get(final Class clazz, final Iterable ids) {
        return find(clazz).disableValidation().filter(Mapper.ID_KEY + " in", ids).enableValidation();
    }

    @Override
    public  T get(final Class clazz, final V id) {
        return find(getCollection(clazz).getName(), clazz, Mapper.ID_KEY, id, 0, 1, true).get();
    }

    @Override
    @SuppressWarnings("unchecked")
    public  T get(final T entity) {
        final T unwrapped = ProxyHelper.unwrap(entity);
        final Object id = mapper.getId(unwrapped);
        if (id == null) {
            throw new MappingException("Could not get id for " + unwrapped.getClass().getName());
        }
        return (T) get(unwrapped.getClass(), id);
    }

    @Override
    public  T getByKey(final Class clazz, final Key key) {
        final String collectionName = mapper.getCollectionName(clazz);
        final String keyCollection = mapper.updateCollection(key);
        if (!collectionName.equals(keyCollection)) {
            throw new RuntimeException("collection names don't match for key and class: " + collectionName + " != " + keyCollection);
        }

        Object id = key.getId();
        if (id instanceof DBObject) {
            ((DBObject) id).removeField(Mapper.CLASS_NAME_FIELDNAME);
        }
        return get(clazz, id);
    }

    @Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public  List getByKeys(final Class clazz, final Iterable> keys) {

        final Map> kindMap = new HashMap>();
        final List entities = new ArrayList();
        // String clazzKind = (clazz==null) ? null :
        // getMapper().getCollectionName(clazz);
        for (final Key key : keys) {
            mapper.updateCollection(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.getCollection())) {
                kindMap.get(key.getCollection()).add(key);
            } else {
                kindMap.put(key.getCollection(), new ArrayList(singletonList((Key) key)));
            }
        }
        for (final Map.Entry> entry : kindMap.entrySet()) {
            final List kindKeys = entry.getValue();

            final List objIds = new ArrayList();
            for (final Key key : kindKeys) {
                objIds.add(key.getId());
            }
            final List kindResults = find(entry.getKey(), null).disableValidation().filter("_id in", objIds).asList();
            entities.addAll(kindResults);
        }

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

    @Override
    public  List getByKeys(final Iterable> keys) {
        return getByKeys(null, keys);
    }

    /**
     * @param obj the value to search with
     * @return the DBCollection
     * @deprecated this is an internal method.  no replacement is planned.
     */
    @Deprecated
    public DBCollection getCollection(final Object obj) {
        if (obj == null) {
            return null;
        }
        return getCollection(obj instanceof Class ? (Class) obj : obj.getClass());
    }

    @Override
    public DBCollection getCollection(final Class clazz) {
        final String collName = mapper.getCollectionName(clazz);
        return getDB().getCollection(collName);
    }

    private  MongoCollection getMongoCollection(final Class clazz) {
        return getMongoCollection(mapper.getCollectionName(clazz), clazz);
    }

    private  MongoCollection getMongoCollection(final String name, final Class clazz) {
        return database.getCollection(name, clazz);
    }

    @Override
    public  long getCount(final T entity) {
        return getCollection(ProxyHelper.unwrap(entity)).count();
    }

    @Override
    public  long getCount(final Class clazz) {
        return getCollection(clazz).count();
    }

    @Override
    public  long getCount(final Query query) {
        return query.count();
    }

    @Override
    public  long getCount(final Query query, final CountOptions options) {
        return query.count(options);
    }

    @Override
    public DB getDB() {
        return db;
    }

    private MongoDatabase getDatabase() {
        return mongoClient.getDatabase(db.getName());
    }

    @Override
    public WriteConcern getDefaultWriteConcern() {
        return defConcern;
    }

    @Override
    public void setDefaultWriteConcern(final WriteConcern wc) {
        defConcern = wc;
    }

    @Override
    @Deprecated
    // use mapper instead.
    public  Key getKey(final T entity) {
        return mapper.getKey(entity);
    }

    @Override
    public MongoClient getMongo() {
        return mongoClient;
    }

    @Override
    public QueryFactory getQueryFactory() {
        return queryFactory;
    }

    @Override
    public void setQueryFactory(final QueryFactory queryFactory) {
        this.queryFactory = queryFactory;
    }

    @Override
    public  MapreduceResults mapReduce(final MapReduceOptions options) {
        DBCollection collection = options.getQuery().getCollection();

        final EntityCache cache = createCache();
        MapreduceResults results = new MapreduceResults(collection.mapReduce(options.toCommand(getMapper())));

        results.setOutputType(options.getOutputType());

        if (OutputType.INLINE.equals(options.getOutputType())) {
            results.setInlineRequiredOptions(this, options.getResultType(), getMapper(), cache);
        } else {
            results.setQuery(newQuery(options.getResultType(), getDB().getCollection(results.getOutputCollectionName())));
        }

        return results;

    }

    @Override
    @Deprecated
    public  MapreduceResults mapReduce(final MapreduceType type, final Query query, final String map, final String reduce,
                                             final String finalize, final Map scopeFields, final Class outputType) {

        final DBCollection dbColl = query.getCollection();

        final String outColl = mapper.getCollectionName(outputType);

        final MapReduceCommand cmd = new MapReduceCommand(dbColl, map, reduce, outColl, type.toOutputType(), 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.isEmpty()) {
            cmd.setScope(scopeFields);
        }

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

    @Override
    @Deprecated
    public  MapreduceResults mapReduce(final MapreduceType type, final Query query, final Class outputType,
                                             final MapReduceCommand baseCommand) {

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

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

        final OutputType outType = type.toOutputType();

        final DBCollection dbColl = query.getCollection();

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

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

        if (LOG.isTraceEnabled()) {
            LOG.info("Executing " + cmd.toString());
        }

        final EntityCache cache = createCache();
        MapreduceResults results = new MapreduceResults(dbColl.mapReduce(baseCommand));

        results.setType(type);
        if (MapreduceType.INLINE.equals(type)) {
            results.setInlineRequiredOptions(this, outputType, getMapper(), cache);
        } else {
            results.setQuery(newQuery(outputType, getDB().getCollection(results.getOutputCollectionName())));
        }

        return results;

    }

    @Override
    public  Key merge(final T entity) {
        return merge(entity, getWriteConcern(entity));
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Key merge(final T entity, final WriteConcern wc) {
        T unwrapped = entity;
        final LinkedHashMap involvedObjects = new LinkedHashMap();
        final DBObject dbObj = mapper.toDBObject(unwrapped, involvedObjects);
        final Key key = mapper.getKey(unwrapped);
        unwrapped = ProxyHelper.unwrap(unwrapped);
        final Object id = mapper.getId(unwrapped);
        if (id == null) {
            throw new MappingException("Could not get id for " + unwrapped.getClass().getName());
        }

        // remove (immutable) _id field for update.
        final Object idValue = dbObj.get(Mapper.ID_KEY);
        dbObj.removeField(Mapper.ID_KEY);

        WriteResult wr;

        final MappedClass mc = mapper.getMappedClass(unwrapped);
        final DBCollection dbColl = getCollection(unwrapped);

        // try to do an update if there is a @Version field
        wr = tryVersionedUpdate(dbColl, unwrapped, dbObj, idValue, new InsertOptions().writeConcern(wc), mc);

        if (wr == null) {
            final Query query = (Query) createQuery(unwrapped.getClass()).filter(Mapper.ID_KEY, id);
            wr = update(query, new BasicDBObject("$set", dbObj), false, false, wc).getWriteResult();
        }

        final UpdateResults res = new UpdateResults(wr);

        if (res.getUpdatedCount() == 0) {
            throw new UpdateException("Nothing updated");
        }

        dbObj.put(Mapper.ID_KEY, idValue);
        postSaveOperations(Collections.singletonList(entity), involvedObjects, dbColl, false);
        return key;
    }

    @Override
    public  Query queryByExample(final T ex) {
        return queryByExample(getCollection(ex), ex);
    }

    @Override
    public  Iterable> save(final Iterable entities) {
        Iterator iterator = entities.iterator();
        return !iterator.hasNext()
               ? Collections.>emptyList()
               : save(entities, getWriteConcern(iterator.next()));
    }

    @Override
    public  Iterable> save(final Iterable entities, final WriteConcern wc) {
        return save(entities, new InsertOptions().writeConcern(wc));
    }

    @Override
    public  Iterable> save(final Iterable entities, final InsertOptions options) {
        final List> savedKeys = new ArrayList>();
        for (final T ent : entities) {
            savedKeys.add(save(ent, options));
        }
        return savedKeys;

    }

    @Override
    @Deprecated
    public  Iterable> save(final T... entities) {
        return save(asList(entities), new InsertOptions());
    }

    @Override
    public  Key save(final T entity) {
        return save(entity, new InsertOptions());
    }

    @Override
    @Deprecated
    public  Key save(final T entity, final WriteConcern wc) {
        return save(entity, new InsertOptions()
            .writeConcern(wc));
    }

    @Override
    public  Key save(final T entity, final InsertOptions options) {
        if (entity == null) {
            throw new UpdateException("Can not persist a null entity");
        }

        final T unwrapped = ProxyHelper.unwrap(entity);
        return save(getCollection(unwrapped), unwrapped, enforceWriteConcern(options, entity.getClass()));
    }

    @Override
    @SuppressWarnings("unchecked")
    public  UpdateResults update(final T entity, final UpdateOperations operations) {
        if (entity instanceof Query) {
            return update((Query) entity, operations);
        }

        final MappedClass mc = mapper.getMappedClass(entity);
        Query query = createQuery(mapper.getMappedClass(entity).getClazz())
            .disableValidation()
            .filter(Mapper.ID_KEY, mapper.getId(entity));
        if (!mc.getFieldsAnnotatedWith(Version.class).isEmpty()) {
            final MappedField field = mc.getFieldsAnnotatedWith(Version.class).get(0);
            query.field(field.getNameToStore()).equal(field.getFieldValue(entity));
        }

        return update((Query) query, operations);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  UpdateResults update(final Key key, final UpdateOperations operations) {
        Class clazz = (Class) key.getType();
        if (clazz == null) {
            clazz = (Class) mapper.getClassFromCollection(key.getCollection());
        }
        return updateFirst(createQuery(clazz).disableValidation().filter(Mapper.ID_KEY, key.getId()), operations);
    }

    @Override
    public  UpdateResults update(final Query query, final UpdateOperations operations) {
        return update(query, operations, new UpdateOptions()
            .upsert(false)
            .multi(true)
            .writeConcern(getWriteConcern(query.getEntityClass())));
    }

    @Override
    public  UpdateResults update(final Query query, final UpdateOperations operations, final boolean createIfMissing) {
        return update(query, operations, new UpdateOptions()
            .upsert(createIfMissing)
            .writeConcern(getWriteConcern(query.getEntityClass())));
    }

    @Override
    public  UpdateResults update(final Query query, final UpdateOperations operations, final boolean createIfMissing,
                                    final WriteConcern wc) {
        return update(query, operations, new UpdateOptions()
                    .upsert(createIfMissing)
                    .multi(true)
                    .writeConcern(wc));
    }

    @Override
    public  UpdateResults updateFirst(final Query query, final UpdateOperations operations) {
        return update(query, operations, new UpdateOptions());
    }

    @Override
    public  UpdateResults updateFirst(final Query query, final UpdateOperations operations, final boolean createIfMissing) {
        return update(query, operations, new UpdateOptions().upsert(createIfMissing));

    }

    @Override
    public  UpdateResults updateFirst(final Query query, final UpdateOperations operations, final boolean createIfMissing,
                                         final WriteConcern wc) {
        return update(query, operations, new UpdateOptions()
            .upsert(createIfMissing)
            .writeConcern(wc));
    }

    @Override
    public  UpdateResults updateFirst(final Query query, final T entity, final boolean createIfMissing) {
        if (getMapper().getMappedClass(entity).getMappedVersionField() != null) {
            throw new UnsupportedOperationException("updateFirst() is not supported with versioned entities");
        }

        final LinkedHashMap involvedObjects = new LinkedHashMap();
        final DBObject dbObj = mapper.toDBObject(entity, involvedObjects);

        final UpdateResults res = update(query, dbObj, createIfMissing, false, getWriteConcern(entity));

        // update _id field
        if (res.getInsertedCount() > 0) {
            dbObj.put(Mapper.ID_KEY, res.getNewId());
        }

        postSaveOperations(singletonList(entity), involvedObjects, getCollection(entity), false);
        return res;
    }

    @Override
    public  Query createQuery(final String collection, final Class type) {
        return newQuery(type, getDB().getCollection(collection));
    }

    @Override
    public  Query createQuery(final Class clazz, final DBObject q) {
        return newQuery(clazz, getCollection(clazz), q);
    }

    @Override
    public  Query createQuery(final String collection, final Class type, final DBObject q) {
        return newQuery(type, getCollection(collection), q);
    }

    @Override
    public  DBRef createRef(final Class clazz, final V id) {
        if (id == null) {
            throw new MappingException("Could not get id for " + clazz.getName());
        }
        return new DBRef(getCollection(clazz).getName(), id);
    }

    @Override
    public  DBRef createRef(final T entity) {
        final T wrapped = ProxyHelper.unwrap(entity);
        final Object id = mapper.getId(wrapped);
        if (id == null) {
            throw new MappingException("Could not get id for " + wrapped.getClass().getName());
        }
        return createRef(wrapped.getClass(), id);
    }

    @Override
    public  UpdateOperations createUpdateOperations(final Class type, final DBObject ops) {
        final UpdateOpsImpl upOps = (UpdateOpsImpl) createUpdateOperations(type);
        upOps.setOps(ops);
        return upOps;
    }

    @Override
    public  WriteResult delete(final String kind, final Class clazz, final V id) {
        return delete(find(kind, clazz).filter(Mapper.ID_KEY, id));
    }

    @Override
    public  WriteResult delete(final String kind, final Class clazz, final V id, final DeleteOptions options) {
        return delete(find(kind, clazz).filter(Mapper.ID_KEY, id), options);
    }

    @Override
    @Deprecated
    public  WriteResult delete(final String kind, final Class clazz, final V id, final WriteConcern wc) {
        return delete(find(kind, clazz).filter(Mapper.ID_KEY, id), new DeleteOptions().writeConcern(wc));
    }

    @Override
    @Deprecated
    public  void ensureIndex(final Class type, final String fields) {
        ensureIndex(type, null, fields, false, false);
    }

    @Override
    @Deprecated
    public  void ensureIndex(final Class clazz, final String name, final String fields, final boolean unique,
                                final boolean dropDupsOnCreate) {
        MappedClass mappedClass = getMapper().getMappedClass(clazz);
        ensureIndex(mappedClass.getCollectionName(), clazz, name, fields, unique, dropDupsOnCreate);
    }

    @Override
    public void ensureIndexes() {
        ensureIndexes(false);
    }

    @Override
    public void ensureIndexes(final boolean background) {
        for (final MappedClass mc : mapper.getMappedClasses()) {
            indexHelper.createIndex(getMongoCollection(mc.getClazz()), mc, background);
        }
    }

    @Override
    public  void ensureIndexes(final Class clazz) {
        ensureIndexes(clazz, false);
    }

    @Override
    public  void ensureIndexes(final Class clazz, final boolean background) {
        indexHelper.createIndex(getMongoCollection(clazz), mapper.getMappedClass(clazz), background);
    }

    @Override
    @Deprecated
    public  void ensureIndex(final String collection, final Class type, final String fields) {
        ensureIndex(collection, type, null, fields, false, false);
    }

    @Override
    @Deprecated
    public  void ensureIndex(final String collection, final Class clazz, final String name, final String fields, final boolean unique,
                                final boolean dropDupsOnCreate) {
        if (dropDupsOnCreate) {
            LOG.warning("Support for dropDups has been removed from the server.  Please remove this setting.");
        }

        indexHelper.createIndex(getMongoCollection(collection, clazz), getMapper().getMappedClass(clazz),
                                new IndexBuilder()
                                    .fields(fields)
                                    .name(name)
                                    .unique(unique), false);
    }

    @Override
    public  void ensureIndexes(final String collection, final Class clazz) {
        ensureIndexes(collection, clazz, false);
    }

    @Override
    public  void ensureIndexes(final String collection, final Class clazz, final boolean background) {
        indexHelper.createIndex(getMongoCollection(collection, clazz), mapper.getMappedClass(clazz), background);
    }

    @Override
    public Key exists(final Object entityOrKey, final ReadPreference readPreference) {
        final Query query = buildExistsQuery(entityOrKey);
        if (readPreference != null) {
            query.useReadPreference(readPreference);
        }
        return query.getKey();
    }

    @Override
    public  Query find(final String collection, final Class clazz) {
        return createQuery(collection, clazz);
    }

    @Override
    public  Query find(final String collection, final Class clazz, final String property, final V value, final int offset,
                                final int size) {
        return find(collection, clazz, property, value, offset, size, true);
    }

    @Override
    public  T get(final Class clazz, final DBRef ref) {
        DBObject object = getDB().getCollection(ref.getCollectionName()).findOne(new BasicDBObject("_id", ref.getId()));
        return mapper.fromDBObject(this, clazz, object, createCache());
    }

    @Override
    public  T get(final String collection, final Class clazz, final V id) {
        final List results = find(collection, clazz, Mapper.ID_KEY, id, 0, 1).asList();
        if (results == null || results.isEmpty()) {
            return null;
        }
        return results.get(0);
    }

    @Override
    public long getCount(final String collection) {
        return getCollection(collection).count();
    }

    @Override
    public DBDecoderFactory getDecoderFact() {
        return decoderFactory != null ? decoderFactory : DefaultDBDecoder.FACTORY;
    }

    @Override
    public void setDecoderFact(final DBDecoderFactory fact) {
        decoderFactory = fact;
    }

    @Override
    public  Key insert(final String collection, final T entity) {
        final T unwrapped = ProxyHelper.unwrap(entity);
        return insert(getCollection(collection), unwrapped, new InsertOptions()
            .writeConcern(getWriteConcern(unwrapped)));
    }

    @Override
    public  Key insert(final String collection, final T entity, final InsertOptions options) {
        return insert(getCollection(collection), ProxyHelper.unwrap(entity), options);
    }

    @Override
    public  Key insert(final T entity) {
        return insert(entity, getWriteConcern(entity));
    }

    @Override
    public  Key insert(final T entity, final WriteConcern wc) {
        return insert(entity, new InsertOptions().writeConcern(wc));
    }

    @Override
    public  Key insert(final T entity, final InsertOptions options) {
        final T unwrapped = ProxyHelper.unwrap(entity);
        return insert(getCollection(unwrapped), unwrapped, options);
    }

    @Override
    @Deprecated
    public  Iterable> insert(final T... entities) {
        return insert(asList(entities));
    }

    @Override
    public  Iterable> insert(final Iterable entities, final WriteConcern wc) {
        return insert(entities, new InsertOptions().writeConcern(wc));
    }

    @Override
    public  Iterable> insert(final Iterable entities, final InsertOptions options) {
        Iterator iterator = entities.iterator();
        return !iterator.hasNext()
               ? Collections.>emptyList()
               : insert(getCollection(iterator.next()), entities, options);
    }

    @Override
    public  Iterable> insert(final String collection, final Iterable entities) {
        return insert(collection, entities, new InsertOptions());
    }

    @Override
    @Deprecated
    public  Iterable> insert(final String collection, final Iterable entities, final WriteConcern wc) {
        return insert(getDB().getCollection(collection), entities, new InsertOptions()
            .writeConcern(wc));
    }

    @Override
    @Deprecated
    public  Iterable> insert(final String collection, final Iterable entities, final InsertOptions options) {
        return insert(getDB().getCollection(collection), entities, options);
    }

    @Override
    public  Query queryByExample(final String collection, final T ex) {
        return queryByExample(getDB().getCollection(collection), ex);
    }

    @Override
    public  Key save(final String collection, final T entity) {
        final T unwrapped = ProxyHelper.unwrap(entity);
        return save(collection, entity, getWriteConcern(unwrapped));
    }

    @Override
    public  Key save(final String collection, final T entity, final WriteConcern wc) {
        return save(getCollection(collection), ProxyHelper.unwrap(entity), new InsertOptions().writeConcern(wc));
    }

    @Override
    public  Key save(final String collection, final T entity, final InsertOptions options) {
        return save(getCollection(collection), ProxyHelper.unwrap(entity), options);
    }

    /**
     * Deletes entities based on the query, with the WriteConcern
     *
     * @param clazz the clazz to query against when finding documents to delete
     * @param id    the ID to look for
     * @param wc    the WriteConcern to use when deleting
     * @param    the type to delete
     * @param    the type of the key
     * @return results of the delete
     * @deprecated use {@link #delete(Class, Object, DeleteOptions)}
     */
    @Deprecated
    public  WriteResult delete(final Class clazz, final V id, final WriteConcern wc) {
        return delete(createQuery(clazz).filter(Mapper.ID_KEY, id), new DeleteOptions().writeConcern(wc));
    }

    /**
     * Find all instances by type in a different collection than what is mapped on the class given skipping some documents and returning a
     * fixed number of the remaining.
     *
     * @param collection The collection use when querying
     * @param clazz      the class to use for mapping the results
     * @param property   the document property to query against
     * @param value      the value to check for
     * @param offset     the number of results to skip
     * @param size       the maximum number of results to return
     * @param validate   if true, validate the query
     * @param         the type to query
     * @param         the type to filter value
     * @return the query
     */
    public  Query find(final String collection, final Class clazz, final String property, final V value, final int offset,
                                final int size, final boolean validate) {
        final Query query = find(collection, clazz);
        if (!validate) {
            query.disableValidation();
        }
        query.offset(offset);
        query.limit(size);
        return query.filter(property, value).enableValidation();
    }

    /**
     * @return the Mapper used by this Datastore
     */
    public Mapper getMapper() {
        return mapper;
    }

    /**
     * Sets the Mapper this Datastore uses
     *
     * @param mapper the new Mapper
     */
    public void setMapper(final Mapper mapper) {
        this.mapper = mapper;
    }

    /**
     * Inserts entities in to the database
     *
     * @param entities the entities to insert
     * @param       the type of the entities
     * @return the keys of entities
     */
    @Override
    public  Iterable> insert(final Iterable entities) {
        return insert(entities, new InsertOptions()
            .writeConcern(defConcern));
    }

    /**
     * Inserts an entity in to the database
     *
     * @param collection the collection to query against
     * @param entity     the entity to insert
     * @param wc         the WriteConcern to use when deleting
     * @param         the type of the entities
     * @return the key of entity
     */
    public  Key insert(final String collection, final T entity, final WriteConcern wc) {
        return insert(getCollection(collection), ProxyHelper.unwrap(entity), new InsertOptions().writeConcern(wc));
    }

    protected DBCollection getCollection(final String kind) {
        if (kind == null) {
            return null;
        }
        return getDB().getCollection(kind);
    }

    @Deprecated
    protected Object getId(final Object entity) {
        return mapper.getId(entity);
    }

    protected  Key insert(final DBCollection dbColl, final T entity, final InsertOptions options) {
        final LinkedHashMap involvedObjects = new LinkedHashMap();
        dbColl.insert(singletonList(entityToDBObj(entity, involvedObjects)), enforceWriteConcern(options, entity.getClass())
            .getOptions());

        return postSaveOperations(singletonList(entity), involvedObjects, dbColl).get(0);
    }

     FindAndModifyOptions enforceWriteConcern(final FindAndModifyOptions options, final Class klass) {
        if (options.getWriteConcern() == null) {
            return options
                .copy()
                .writeConcern(getWriteConcern(klass));
        }
        return options;
    }

     InsertOptions enforceWriteConcern(final InsertOptions options, final Class klass) {
        if (options.getWriteConcern() == null) {
            return options
                .copy()
                .writeConcern(getWriteConcern(klass));
        }
        return options;
    }

     UpdateOptions enforceWriteConcern(final UpdateOptions options, final Class klass) {
        if (options.getWriteConcern() == null) {
            return options
                .copy()
                .writeConcern(getWriteConcern(klass));
        }
        return options;
    }

     DeleteOptions enforceWriteConcern(final DeleteOptions options, final Class klass) {
        if (options.getWriteConcern() == null) {
            return options
                .copy()
                .writeConcern(getWriteConcern(klass));
        }
        return options;
    }

    protected  Key save(final DBCollection dbColl, final T entity, final InsertOptions options) {
        if (entity == null) {
            throw new UpdateException("Can not persist a null entity");
        }

        final MappedClass mc = mapper.getMappedClass(entity);
        if (mc.getAnnotation(NotSaved.class) != null) {
            throw new MappingException(format("Entity type: %s is marked as NotSaved which means you should not try to save it!",
                                              mc.getClazz().getName()));
        }

        // 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.
        final LinkedHashMap involvedObjects = new LinkedHashMap();
        final DBObject document = entityToDBObj(entity, involvedObjects);

        // try to do an update if there is a @Version field
        final Object idValue = document.get(Mapper.ID_KEY);
        WriteResult wr = tryVersionedUpdate(dbColl, entity, document, idValue, enforceWriteConcern(options, entity.getClass()), mc);

        if (wr == null) {
            saveDocument(dbColl, document, options);
        }

        return postSaveOperations(singletonList(entity), involvedObjects, dbColl).get(0);
    }

    private WriteResult saveDocument(final DBCollection dbColl, final DBObject document, final InsertOptions options) {
        if (document.get(ID_FIELD_NAME) == null) {
            return dbColl.insert(singletonList(document), options.getOptions());
        } else {
            return dbColl.update(new BasicDBObject(ID_FIELD_NAME, document.get(ID_FIELD_NAME)), document,
                          new DBCollectionUpdateOptions()
                              .bypassDocumentValidation(options.getBypassDocumentValidation())
                              .writeConcern(options.getWriteConcern())
                              .upsert(true));
        }
    }

    private  WriteResult tryVersionedUpdate(final DBCollection dbColl, final T entity, final DBObject dbObj, final Object idValue,
                                               final InsertOptions options, final MappedClass mc) {
        WriteResult wr;
        if (mc.getFieldsAnnotatedWith(Version.class).isEmpty()) {
            return null;
        }

        final MappedField mfVersion = mc.getMappedVersionField();
        final String versionKeyName = mfVersion.getNameToStore();

        Long oldVersion = (Long) mfVersion.getFieldValue(entity);
        long newVersion = nextValue(oldVersion);

        dbObj.put(versionKeyName, newVersion);
        //        mfVersion.setFieldValue(entity, newVersion);

        if (idValue != null && newVersion != 1) {
            final Query query = find(dbColl.getName(), entity.getClass())
                .disableValidation()
                .filter(Mapper.ID_KEY, idValue)
                .enableValidation()
                .filter(versionKeyName, oldVersion);
            final UpdateResults res = update(query, dbObj, new UpdateOptions()
                .bypassDocumentValidation(options.getBypassDocumentValidation())
                .writeConcern(options.getWriteConcern()));

            wr = res.getWriteResult();

            if (res.getUpdatedCount() != 1) {
                throw new ConcurrentModificationException(format("Entity of class %s (id='%s',version='%d') was concurrently updated.",
                                                                 entity.getClass().getName(), idValue, oldVersion));
            }
        } else {
            wr = saveDocument(dbColl, dbObj, options);
        }

        return wr;
    }

    private Query buildExistsQuery(final Object entityOrKey) {
        final Object unwrapped = ProxyHelper.unwrap(entityOrKey);
        final Key key = mapper.getKey(unwrapped);
        final Object id = key.getId();
        if (id == null) {
            throw new MappingException("Could not get id for " + unwrapped.getClass().getName());
        }

        return find(key.getCollection(), key.getType()).filter(Mapper.ID_KEY, key.getId());
    }

    private EntityCache createCache() {
        return mapper.createEntityCache();
    }

    private DBObject entityToDBObj(final Object entity, final Map involvedObjects) {
        return mapper.toDBObject(ProxyHelper.unwrap(entity), involvedObjects);
    }

    private  Iterable> insert(final DBCollection dbColl, final Iterable entities, final InsertOptions options) {
        if (!entities.iterator().hasNext()) {
            return Collections.emptyList();
        }

        final Map involvedObjects = new LinkedHashMap();
        final List list = new ArrayList();
        com.mongodb.InsertOptions insertOptions = options.getOptions();
        for (final T entity : entities) {
            if (options.getWriteConcern() == null) {
                insertOptions = enforceWriteConcern(options, entity.getClass()).getOptions();
            }
            list.add(toDbObject(entity, involvedObjects));
        }
        dbColl.insert(list, insertOptions);

        return postSaveOperations(entities, involvedObjects, dbColl);
    }

    /**
     * Creates and returns a {@link Query} using the underlying {@link QueryFactory}.
     *
     * @see QueryFactory#createQuery(Datastore, DBCollection, Class, DBObject)
     */
    private  Query newQuery(final Class type, final DBCollection collection, final DBObject query) {
        return getQueryFactory().createQuery(this, collection, type, query);
    }

    /**
     * Creates and returns a {@link Query} using the underlying {@link QueryFactory}.
     *
     * @see QueryFactory#createQuery(Datastore, DBCollection, Class)
     */
    private  Query newQuery(final Class type, final DBCollection collection) {
        return getQueryFactory().createQuery(this, collection, type);
    }

    private long nextValue(final Long oldVersion) {
        return oldVersion == null ? 1 : oldVersion + 1;
    }

    private  List> postSaveOperations(final Iterable entities, final Map involvedObjects,
                                                final DBCollection collection) {
        return postSaveOperations(entities, involvedObjects, collection, true);
    }

    @SuppressWarnings("unchecked")
    private  List> postSaveOperations(final Iterable entities, final Map involvedObjects,
                                                final DBCollection collection, final boolean fetchKeys) {
        List> keys = new ArrayList>();
        for (final T entity : entities) {
            final DBObject dbObj = involvedObjects.remove(entity);

            if (fetchKeys) {
                if (dbObj.get(Mapper.ID_KEY) == null) {
                    throw new MappingException(format("Missing _id after save on %s", entity.getClass().getName()));
                }
                mapper.updateKeyAndVersionInfo(this, dbObj, createCache(), entity);
                keys.add(new Key((Class) entity.getClass(), collection.getName(), mapper.getId(entity)));
            }
            mapper.getMappedClass(entity).callLifecycleMethods(PostPersist.class, entity, dbObj, mapper);
        }

        for (Entry entry : involvedObjects.entrySet()) {
            final Object key = entry.getKey();
            mapper.getMappedClass(key).callLifecycleMethods(PostPersist.class, key, entry.getValue(), mapper);

        }
        return keys;
    }

    @SuppressWarnings("unchecked")
    private  Query queryByExample(final DBCollection coll, final T example) {
        // TODO: think about remove className from baseQuery param below.
        final Class type = (Class) example.getClass();
        final DBObject query = entityToDBObj(example, new HashMap());
        return newQuery(type, coll, query);
    }

    private  DBObject toDbObject(final T ent, final Map involvedObjects) {
        final MappedClass mc = mapper.getMappedClass(ent);
        if (mc.getAnnotation(NotSaved.class) != null) {
            throw new MappingException(format("Entity type: %s is marked as NotSaved which means you should not try to save it!",
                                              mc.getClazz().getName()));
        }
        DBObject dbObject = entityToDBObj(ent, involvedObjects);
        List versionFields = mc.getFieldsAnnotatedWith(Version.class);
        for (MappedField mappedField : versionFields) {
            String name = mappedField.getNameToStore();
            if (dbObject.get(name) == null) {
                dbObject.put(name, 1);
                mappedField.setFieldValue(ent, 1L);
            }
        }
        return dbObject;
    }

    @Override
    public  UpdateResults update(final Query query, final UpdateOperations operations, final UpdateOptions options) {
        DBCollection dbColl = query.getCollection();
        // TODO remove this after testing.
        if (dbColl == null) {
            dbColl = getCollection(query.getEntityClass());
        }

        final MappedClass mc = getMapper().getMappedClass(query.getEntityClass());
        final List fields = mc.getFieldsAnnotatedWith(Version.class);

        DBObject queryObject = query.getQueryObject();
        if (operations.isIsolated()) {
            queryObject.put("$isolated", true);
        }

        if (!fields.isEmpty()) {
            operations.inc(fields.get(0).getNameToStore(), 1);
        }

        final BasicDBObject update = (BasicDBObject) ((UpdateOpsImpl) operations).getOps();
        if (LOG.isTraceEnabled()) {
            LOG.trace(format("Executing update(%s) for query: %s, ops: %s, multi: %s, upsert: %s",
                             dbColl.getName(), queryObject, update, options.isMulti(), options.isUpsert()));
        }

        return new UpdateResults(dbColl.update(queryObject, update,
                                               enforceWriteConcern(options, query.getEntityClass())
                                                   .getOptions()));
    }

    @SuppressWarnings("unchecked")
    private  UpdateResults update(final Query query, final DBObject update, final boolean createIfMissing, final boolean multi,
                                     final WriteConcern wc) {
        return  update(query, update, new UpdateOptions()
                      .upsert(createIfMissing)
                      .multi(multi)
                      .writeConcern(wc));
    }

    @SuppressWarnings("unchecked")
    private  UpdateResults update(final Query query, final DBObject update, final UpdateOptions options) {

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

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

        DBObject queryObject = query.getQueryObject();

        final MappedClass mc = getMapper().getMappedClass(query.getEntityClass());
        final List fields = mc.getFieldsAnnotatedWith(Version.class);
        if (!fields.isEmpty()) {
            final MappedField versionMF = fields.get(0);
            if (update.get(versionMF.getNameToStore()) == null) {
                if (!update.containsField("$inc")) {
                    update.put("$inc", new BasicDBObject(versionMF.getNameToStore(), 1));
                } else {
                    ((Map) (update.get("$inc"))).put(versionMF.getNameToStore(), 1);
                }
            }
        }

        if (LOG.isTraceEnabled()) {
            LOG.trace(format("Executing update(%s) for query: %s, ops: %s, multi: %s, upsert: %s",
                             dbColl.getName(), queryObject, update, options.isMulti(), options.isUpsert()));
        }

        return new UpdateResults(dbColl.update(queryObject, update,
                                               enforceWriteConcern(options, query.getEntityClass())
                                                   .getOptions()));
    }

    /**
     * Gets the write concern for entity or returns the default write concern for this datastore
     *
     * @param clazzOrEntity the class or entity to use when looking up the WriteConcern
     */
    private WriteConcern getWriteConcern(final Object clazzOrEntity) {
        WriteConcern wc = defConcern;
        if (clazzOrEntity != null) {
            final Entity entityAnn = getMapper().getMappedClass(clazzOrEntity).getEntityAnnotation();
            if (entityAnn != null && entityAnn.concern().length() != 0) {
                wc = WriteConcern.valueOf(entityAnn.concern());
            }
        }

        return wc;
    }
}