com.github.jmkgreen.morphia.DatastoreImpl Maven / Gradle / Ivy
The newest version!
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
* @param
* @return
*/
public DBRef createRef(Class 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
* @return
*/
public DBRef createRef(T entity) {
entity = ProxyHelper.unwrap(entity);
Object id = getId(entity);
if (id == null)
throw new MappingException("Could not get id for " + entity.getClass().getName());
return createRef(entity.getClass(), id);
}
/**
* @param entity
* @return
*/
@Deprecated
protected Object getId(Object entity) {
return mapr.getId(entity);
}
/**
* @param entity
* @param
* @return
*/
@Deprecated // use mapper instead.
public Key getKey(T entity) {
return mapr.getKey(entity);
}
/**
* @param kind
* @param id
* @param
* @return
*/
public 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
* @param
* @return
*/
public WriteResult delete(String kind, Class clazz, V id) {
return delete(find(kind, clazz).filter(Mapper.ID_KEY, id));
}
/**
* @param clazz
* @param id
* @param wc
* @param
* @param
* @return
*/
public WriteResult delete(Class clazz, V id, WriteConcern wc) {
return delete(createQuery(clazz).filter(Mapper.ID_KEY, id), wc);
}
/**
* @param clazz
* @param id
* @param
* @param
* @return
*/
public WriteResult delete(Class clazz, V id) {
return delete(clazz, id, getWriteConcern(clazz));
}
/**
* @param clazz
* @param ids
* @param
* @param
* @return
*/
public WriteResult delete(Class clazz, Iterable ids) {
Query q = find(clazz).disableValidation().filter(Mapper.ID_KEY + " in", ids);
return delete(q);
}
/**
* @param entity
* @param
* @return
*/
public WriteResult delete(T entity) {
return delete(entity, getWriteConcern(entity));
}
/**
* @param entity
* @param wc
* @param
* @return
*/
public 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 = getId(entity);
return delete(entity.getClass(), id, wc);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @param query
* @param
* @return
*/
public WriteResult delete(Query query) {
return delete(query, getWriteConcern(query.getEntityClass()));
}
/**
* @param query
* @param wc
* @param
* @return
*/
public WriteResult delete(Query query, WriteConcern wc) {
QueryImpl q = (QueryImpl) query;
DBCollection dbColl = q.getCollection();
//TODO remove this after testing.
if (dbColl == null)
dbColl = getCollection(q.getEntityClass());
WriteResult wr;
if (q.getSortObject() != null || q.getOffset() != 0 || q.getLimit() > 0)
throw new QueryException("Delete does not allow sort/offset/limit query options.");
if (q.getQueryObject() != null)
if (wc == null)
wr = dbColl.remove(q.getQueryObject());
else
wr = dbColl.remove(q.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
*/
public void ensureIndex(Class type, String fields) {
ensureIndex(type, null, fields, false, false);
}
/**
* @param clazz
* @param name
* @param defs
* @param unique
* @param dropDupsOnCreate
* @param
*/
public void ensureIndex(Class clazz, String name, IndexFieldDef[] defs, boolean unique, boolean dropDupsOnCreate) {
ensureIndex(clazz, name, defs, unique, dropDupsOnCreate, false);
}
/**
* @param clazz
* @param name
* @param fields
* @param unique
* @param dropDupsOnCreate
* @param
*/
public void ensureIndex(Class 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
*/
public void ensureIndex(Class 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);
}
/**
* 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 void ensureIndex(Class 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);
}
}
/**
* @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
*/
public void ensureIndex(Class type, String name, IndexDirection dir) {
ensureIndex(type, new IndexFieldDef(name, dir));
}
/**
* @param type
* @param fields
* @param
*/
public void ensureIndex(Class type, IndexFieldDef... fields) {
ensureIndex(type, null, fields, false, false);
}
/**
* @param type
* @param background
* @param fields
* @param
*/
public void ensureIndex(Class type, boolean background, IndexFieldDef... fields) {
ensureIndex(type, null, fields, false, false, background);
}
/**
* 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 void ensureIndexes(Class clazz) {
ensureIndexes(clazz, false);
}
/**
* @param clazz
* @param background
* @param
*/
public void ensureIndexes(Class 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(), new ArrayList());
}
/**
* @param mc
* @param background
* @param parentMCs
* @param parentMFs
*/
protected void ensureIndexes(MappedClass mc, boolean background, ArrayList parentMCs, ArrayList 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 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 parentMCs, ArrayList 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 newParentClasses = (ArrayList) parentMCs.clone();
ArrayList newParents = (ArrayList) 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 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 parentMCs, ArrayList 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())
if (mc.getEntityAnnotation() != null && mc.getEntityAnnotation().cap().value() > 0) {
CappedAt cap = mc.getEntityAnnotation().cap();
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
* @return
*/
public Query queryByExample(T ex) {
return queryByExample(getCollection(ex), ex);
}
/**
* @param kind
* @param ex
* @param
* @return
*/
public Query queryByExample(String kind, T ex) {
return queryByExample(db.getCollection(kind), ex);
}
/**
* @param coll
* @param example
* @param
* @return
*/
private Query queryByExample(DBCollection coll, T example) {
//TODO: think about remove className from baseQuery param below.
return new QueryImpl((Class) example.getClass(), coll, this, entityToDBObj(example, new HashMap