@SuppressWarnings("unchecked") private void initMorphia_() { Properties c = Play.configuration; String dbName = c.getProperty(PREFIX + "name"); if (null == dbName) { warn("mongodb name not configured! using [test] db"); dbName = "test"; } DB db = mongo_.getDB(dbName); if (c.containsKey(PREFIX + "username") && c.containsKey(PREFIX + "password")) { String username = c.getProperty(PREFIX + "username"); String password = c.getProperty(PREFIX + "password"); if (!db.isAuthenticated() && !db.authenticate(username, password.toCharArray())) { throw new RuntimeException("MongoDB authentication failed: " + dbName); } } String loggerClass = c.getProperty("morphia.logger"); Class<? extends LogrFactory> loggerClazz = SilentLogrFactory.class; if (null != loggerClass) { final Pattern P_PLAY = Pattern.compile("(play|enable|true|yes|on)", Pattern.CASE_INSENSITIVE); final Pattern P_SILENT = Pattern.compile("(silent|disable|false|no|off)", Pattern.CASE_INSENSITIVE); if (P_PLAY.matcher(loggerClass).matches()) { loggerClazz = PlayLogrFactory.class; } else if (!P_SILENT.matcher(loggerClass).matches()) { try { loggerClazz = (Class<? extends LogrFactory>) Class.forName(loggerClass); } catch (Exception e) { warn( "Cannot init morphia logger factory using %s. Use PlayLogrFactory instead", loggerClass); } } } loggerRegistered_ = false; MorphiaLoggerFactory.reset(); MorphiaLoggerFactory.registerLogger(loggerClazz); morphia_ = new Morphia(); loggerRegistered_ = true; ds_ = morphia_.createDatastore(mongo_, dbName); dataStores_.put(dbName, ds_); String uploadCollection = c.getProperty("morphia.collection.upload", "uploads"); if (getBooleanProperty("gridfs.enabled")) { gridfs = new GridFS(MorphiaPlugin.ds().getDB(), uploadCollection); } morphia_ .getMapper() .addInterceptor( new AbstractEntityInterceptor() { @Override public void preLoad(Object ent, DBObject dbObj, Mapper mapr) { if (ent instanceof Model) { PlayPlugin.postEvent(MorphiaEvent.ON_LOAD.getId(), ent); ((Model) ent)._h_OnLoad(); } } @Override public void postLoad(Object ent, DBObject dbObj, Mapper mapr) { if (ent instanceof Model) { Model m = (Model) ent; PlayPlugin.postEvent(MorphiaEvent.LOADED.getId(), ent); m._h_Loaded(); } } }); }
static { MorphiaLoggerFactory.reset(); MorphiaLoggerFactory.registerLogger(SLF4JLogrImplFactory.class); }
/** * 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); // final protected Morphia morphia; protected final Mapper mapr; protected final Mongo mongo; protected final DB db; protected WriteConcern defConcern = WriteConcern.SAFE; public DatastoreImpl() { // added by franck for WLED mongo = null; mapr = null; db = null; } public DatastoreImpl(Morphia morphia, Mongo mongo) { this(morphia, mongo, null); } public DatastoreImpl( Morphia morphia, Mongo mongo, String dbName, String username, char[] password) { // this.morphia = morphia; this.mapr = morphia.getMapper(); this.mongo = mongo; this.db = mongo.getDB(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 + "'"); // VERY discussable DatastoreHolder.getInstance().set(this); } public DatastoreImpl(Morphia morphia, Mongo mongo, String dbName) { this(morphia, mongo, dbName, null, null); } 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); } public <T> 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); } @Deprecated protected Object getId(Object entity) { return mapr.getId(entity); } @Deprecated // use mapper instead. public <T> Key<T> getKey(T entity) { return mapr.getKey(entity); } protected <T, V> WriteResult delete(DBCollection dbColl, V id, WriteConcern wc) { WriteResult wr; if (wc == null) wr = dbColl.remove(BasicDBObjectBuilder.start().add(Mapper.ID_KEY, id).get()); else wr = dbColl.remove(BasicDBObjectBuilder.start().add(Mapper.ID_KEY, id).get(), wc); throwOnError(wc, wr); return wr; } public <T> WriteResult delete(String kind, T id) { DBCollection dbColl = getCollection(kind); return delete(dbColl, id, defConcern); } public <T, V> WriteResult delete(Class<T> clazz, V id, WriteConcern wc) { DBCollection dbColl = getCollection(clazz); return delete(dbColl, id, wc); } public <T, V> WriteResult delete(Class<T> clazz, V id) { return delete(clazz, id, getWriteConcern(clazz)); } 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); } public <T> WriteResult delete(T entity) { return delete(entity, getWriteConcern(entity)); } 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 = getId(entity); return delete(entity.getClass(), id, wc); } catch (Exception e) { throw new RuntimeException(e); } } public <T> WriteResult delete(Query<T> query) { return delete(query, getWriteConcern(query.getEntityClass())); } public <T> WriteResult delete(Query<T> query, WriteConcern wc) { QueryImpl<T> q = (QueryImpl<T>) 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; } public <T> void ensureIndex(Class<T> type, String fields) { ensureIndex(type, null, fields, false, false); } public <T> void ensureIndex( Class<T> clazz, String name, IndexFieldDef[] defs, boolean unique, boolean dropDupsOnCreate) { ensureIndex(clazz, name, defs, unique, dropDupsOnCreate, false); } public <T> void ensureIndex( Class<T> clazz, String name, String fields, boolean unique, boolean dropDupsOnCreate) { ensureIndex( clazz, name, QueryImpl.parseSortString(fields), unique, dropDupsOnCreate, false, false); } public <T> void ensureIndex( Class<T> clazz, String name, String fields, boolean unique, boolean dropDupsOnCreate, boolean background) { ensureIndex( clazz, name, QueryImpl.parseSortString(fields), unique, dropDupsOnCreate, background, false); } protected <T> void ensureIndex( Class<T> clazz, String name, BasicDBObject fields, boolean unique, boolean dropDupsOnCreate, boolean background, boolean sparse) { // validate field names and translate them to the stored values BasicDBObject keys = new BasicDBObject(); for (Entry<String, Object> entry : fields.entrySet()) { StringBuffer sb = new StringBuffer(entry.getKey()); Mapper.validate(clazz, mapr, sb, FilterOperator.IN, "", true, false); keys.put(sb.toString(), entry.getValue()); } 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); DBCollection dbColl = getCollection(clazz); BasicDBObject opts = (BasicDBObject) keyOpts.get(); if (opts.isEmpty()) { log.debug("Ensuring index for " + dbColl.getName() + " with keys:" + keys); dbColl.ensureIndex(keys); } else { log.debug( "Ensuring index for " + dbColl.getName() + " with keys:" + keys + " and opts:" + opts); dbColl.ensureIndex(keys, opts); } // TODO: remove this once using 2.4 driver does this in ensureIndex CommandResult cr = dbColl.getDB().getLastError(); cr.throwOnError(); } @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); } public <T> void ensureIndex(Class<T> type, String name, IndexDirection dir) { ensureIndex(type, new IndexFieldDef(name, dir)); } public <T> void ensureIndex(Class<T> type, IndexFieldDef... fields) { ensureIndex(type, null, fields, false, false); } public <T> void ensureIndex(Class<T> type, boolean background, IndexFieldDef... fields) { ensureIndex(type, null, fields, false, false, background); } protected void ensureIndexes(MappedClass mc, boolean background) { ensureIndexes(mc, background, new ArrayList<MappedClass>(), new ArrayList<MappedField>()); } 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 == null || parentMCs.isEmpty())) return; // Ensure indexes from class annotation Indexes idxs = (Indexes) mc.getAnnotation(Indexes.class); if (idxs != null && idxs.value() != null && idxs.value().length > 0) for (Index index : idxs.value()) { BasicDBObject fields = QueryImpl.parseSortString(index.value()); ensureIndex( mc.getClazz(), index.name(), fields, index.unique(), index.dropDups(), index.background() ? index.background() : background, index.sparse() ? index.sparse() : false); } // Ensure indexes from field annotations, and embedded entities for (MappedField mf : mc.getPersistenceFields()) { if (mf.hasAnnotation(Indexed.class)) { Indexed index = mf.getAnnotation(Indexed.class); StringBuilder field = new StringBuilder(); Class<?> indexedClass = (parentMCs.isEmpty() ? mc : parentMCs.get(0)).getClazz(); if (!parentMCs.isEmpty()) for (MappedField pmf : parentMFs) field.append(pmf.getNameToStore()).append("."); field.append(mf.getNameToStore()); 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); } 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); } } } public <T> void ensureIndexes(Class<T> clazz) { ensureIndexes(clazz, false); } public <T> void ensureIndexes(Class<T> clazz, boolean background) { MappedClass mc = mapr.getMappedClass(clazz); ensureIndexes(mc, background); } public void ensureIndexes() { ensureIndexes(false); } public void ensureIndexes(boolean background) { // loops over mappedClasses and call ensureIndex for each @Entity object // (for now) for (MappedClass mc : mapr.getMappedClasses()) { ensureIndexes(mc, background); } } 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); } } } public <T> Query<T> createQuery(Class<T> clazz) { return new QueryImpl<T>(clazz, getCollection(clazz), this); } public <T> Query<T> createQuery(Class<T> kind, DBObject q) { return new QueryImpl<T>(kind, getCollection(kind), this, q); } public <T> Query<T> createQuery(String kind, Class<T> clazz, DBObject q) { return new QueryImpl<T>(clazz, db.getCollection(kind), this, q); } public <T> Query<T> createQuery(String kind, Class<T> clazz) { return new QueryImpl<T>(clazz, db.getCollection(kind), this); } public <T> Query<T> find(String kind, Class<T> clazz) { return new QueryImpl<T>(clazz, getCollection(kind), this); } public <T> Query<T> find(Class<T> clazz) { return createQuery(clazz); } public <T, V> Query<T> find(Class<T> clazz, String property, V value) { Query<T> query = createQuery(clazz); return query.filter(property, value); } public <T, V> Query<T> find( String kind, Class<T> clazz, String property, V value, int offset, int size) { Query<T> query = find(kind, clazz); query.offset(offset); query.limit(size); return query.filter(property, value); } 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); } public <T> T get(Class<T> clazz, DBRef ref) { return (T) mapr.fromDBObject(clazz, ref.fetch(), createCache()); } public <T, V> Query<T> get(Class<T> clazz, Iterable<V> ids) { return find(clazz).disableValidation().filter(Mapper.ID_KEY + " in", ids); } /** 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; } public <T> List<T> getByKeys(Iterable<Key<T>> keys) { return this.getByKeys((Class<T>) null, keys); } @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); for (Key key : kindKeys) { objIds.add(key.getId()); } List kindResults = find(kind, null).disableValidation().filter("_id in", objIds).asList(); entities.addAll(kindResults); } // TODO: order them based on the incoming Keys. return entities; } 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); } public <T, V> T get(Class<T> clazz, V id) { return find(getCollection(clazz).getName(), clazz, Mapper.ID_KEY, id, 0, 1).get(); } 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()); } public <T> T get(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 (T) get(entity.getClass(), id); } public Key<?> exists(Object entityOrKey) { entityOrKey = ProxyHelper.unwrap(entityOrKey); Key<?> key = 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(); } @SuppressWarnings("rawtypes") public DBCollection getCollection(Class clazz) { String collName = mapr.getCollectionName(clazz); DBCollection dbC = getDB().getCollection(collName); return dbC; } public DBCollection getCollection(Object obj) { if (obj == null) return null; return getCollection(obj.getClass()); } protected DBCollection getCollection(String kind) { if (kind == null) return null; return getDB().getCollection(kind); } public <T> long getCount(T entity) { entity = ProxyHelper.unwrap(entity); return getCollection(entity).getCount(); } public <T> long getCount(Class<T> clazz) { return getCollection(clazz).getCount(); } public long getCount(String kind) { return getCollection(kind).getCount(); } public <T> long getCount(Query<T> query) { return query.countAll(); } public Mongo getMongo() { return this.mongo; } public DB getDB() { return db; } public Mapper getMapper() { return mapr; } 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)); } public <T> Iterable<Key<T>> insert(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>(); T lastEntity = null; for (T ent : entities) { ents.add(entityToDBObj(ent, involvedObjects)); lastEntity = ent; } DBCollection dbColl = getCollection(lastEntity); 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; } public <T> Iterable<Key<T>> insert(T... entities) { return insert(Arrays.asList(entities), getWriteConcern(entities[0])); } public <T> Key<T> insert(T entity) { return insert(entity, getWriteConcern(entity)); } public <T> Key<T> insert(T entity, WriteConcern wc) { entity = ProxyHelper.unwrap(entity); DBCollection dbColl = getCollection(entity); return insert(dbColl, entity, wc); } public <T> Key<T> insert(String kind, T entity) { entity = ProxyHelper.unwrap(entity); DBCollection dbColl = getCollection(kind); return insert(dbColl, entity, getWriteConcern(entity)); } public <T> Key<T> insert(String kind, T entity, WriteConcern wc) { entity = ProxyHelper.unwrap(entity); DBCollection dbColl = getCollection(kind); return insert(dbColl, entity, wc); } 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); } 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(), getId(entity)); key.setKindClass((Class<? extends T>) entity.getClass()); return key; } 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)); } 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; } 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; } protected <T> Key<T> save(DBCollection dbColl, T entity, WriteConcern wc) { MappedClass mc = mapr.getMappedClass(entity); 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 wr = tryVersionedUpdate(dbColl, entity, dbObj, wc, db, mc); if (wr == null) if (wc == null) wr = dbColl.save(dbObj); else wr = dbColl.save(dbObj, wc); throwOnError(wc, wr); return postSaveGetKey(entity, dbObj, dbColl, involvedObjects); } protected <T> WriteResult tryVersionedUpdate( DBCollection dbColl, T entity, DBObject dbObj, WriteConcern wc, DB db, MappedClass mc) { WriteResult wr = null; if (mc.getFieldsAnnotatedWith(Version.class).isEmpty()) return wr; 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((Class<T>) entity.getClass(), 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; } 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(); } } public <T> Key<T> save(String kind, T entity) { entity = ProxyHelper.unwrap(entity); DBCollection dbColl = getCollection(kind); return save(dbColl, entity, getWriteConcern(entity)); } public <T> Key<T> save(T entity) { return save(entity, getWriteConcern(entity)); } public <T> Key<T> save(T entity, WriteConcern wc) { entity = ProxyHelper.unwrap(entity); DBCollection dbColl = getCollection(entity); return save(dbColl, entity, wc); } public <T> UpdateOperations<T> createUpdateOperations(Class<T> clazz) { return new UpdateOpsImpl<T>(clazz, getMapper()); } public <T> UpdateOperations<T> createUpdateOperations(Class<T> kind, DBObject ops) { UpdateOpsImpl<T> upOps = (UpdateOpsImpl<T>) createUpdateOperations(kind); upOps.setOps(ops); return upOps; } public <T> UpdateResults<T> update( Query<T> query, UpdateOperations<T> ops, boolean createIfMissing) { return update(query, ops, createIfMissing, getWriteConcern(query.getEntityClass())); } public <T> UpdateResults<T> update( Query<T> query, UpdateOperations<T> ops, boolean createIfMissing, WriteConcern wc) { return update(query, ops, createIfMissing, false); } public <T> UpdateResults<T> update(T ent, UpdateOperations<T> ops) { MappedClass mc = mapr.getMappedClass(ent); Query<T> q = (Query<T>) createQuery(mc.getClazz()); q.disableValidation().filter(Mapper.ID_KEY, 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); } 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); } public <T> UpdateResults<T> update(Query<T> query, UpdateOperations<T> ops) { return update(query, ops, false, true); } public <T> UpdateResults<T> updateFirst(Query<T> query, UpdateOperations<T> ops) { return update(query, ops, false, false); } public <T> UpdateResults<T> updateFirst( Query<T> query, UpdateOperations<T> ops, boolean createIfMissing) { return update(query, ops, createIfMissing, getWriteConcern(query.getEntityClass())); } public <T> UpdateResults<T> updateFirst( Query<T> query, UpdateOperations<T> ops, boolean createIfMissing, WriteConcern wc) { return update(query, ops, createIfMissing, false, wc); } 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; } public <T> Key<T> merge(T entity) { return merge(entity, getWriteConcern(entity)); } 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 = getKey(entity); entity = ProxyHelper.unwrap(entity); Object id = getId(entity); if (id == null) throw new MappingException("Could not get id for " + entity.getClass().getName()); Query<T> query = (Query<T>) createQuery(entity.getClass()).filter(Mapper.ID_KEY, id); // remove (immutable) _id field for update. dbObj.removeField(Mapper.ID_KEY); UpdateResults<T> res = update(query, new BasicDBObject("$set", dbObj), false, false, wc); // check for updated count if we have a gle CommandResult gle = res.getWriteResult().getCachedLastError(); if (gle != null && res.getUpdatedCount() == 0) throw new UpdateException("Not updated: " + gle); postSaveOperations(entity, dbObj, involvedObjects); return key; } 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(entity); mc.callLifecycleMethods(PostPersist.class, ent, dbO, mapr); } } @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); } @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())); } 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); } 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; } public <T> T findAndModify(Query<T> q, UpdateOperations<T> ops) { return findAndModify(q, ops, false); } public <T> T findAndModify(Query<T> query, UpdateOperations<T> ops, boolean oldVersion) { return findAndModify(query, ops, oldVersion, false); } public <T> T findAndModify( Query<T> query, UpdateOperations<T> ops, boolean oldVersion, boolean createIfMissing) { QueryImpl<T> qi = (QueryImpl<T>) query; DBCollection dbColl = qi.getCollection(); // TODO remove this after testing. if (dbColl == null) dbColl = getCollection(qi.getEntityClass()); if (log.isTraceEnabled()) log.info("Executing findAndModify(" + dbColl.getName() + ") with update "); DBObject res = dbColl.findAndModify( qi.getQueryObject(), qi.getFieldsObject(), qi.getSortObject(), false, ((UpdateOpsImpl<T>) ops).getOps(), !oldVersion, createIfMissing); if (res == null) return null; else return (T) mapr.fromDBObject(qi.getEntityClass(), res, createCache()); } @SuppressWarnings("rawtypes") public <T> MapreduceResults<T> mapReduce( MapreduceType type, Query query, String map, String reduce, String finalize, Map<String, Object> scopeFields, Class<T> outputType) { Assert.parametersNotNull("map", map); Assert.parameterNotEmpty(map, "map"); Assert.parametersNotNull("reduce", reduce); Assert.parameterNotEmpty(reduce, "reduce"); QueryImpl<T> qi = (QueryImpl<T>) query; DBCollection dbColl = qi.getCollection(); // TODO remove this after testing. if (dbColl == null) dbColl = getCollection(qi.getEntityClass()); if (log.isTraceEnabled()) log.info( "Executing mapReduce(" + dbColl.getName() + ") with query(" + qi.toString() + ") map(" + map + ") reduce(" + reduce + ") finalize(" + finalize + ") scope(" + scopeFields + ")"); // TODO replace this with the 2.4 driver impl. String outColl = mapr.getCollectionName(outputType); BasicDBObjectBuilder bldr = BasicDBObjectBuilder.start("mapreduce", mapr.getCollectionName(qi.getEntityClass())); switch (type) { case REDUCE: bldr.push("out").add("reduce", outColl).pop(); break; case MERGE: bldr.push("out").add("merge", outColl).pop(); break; case INLINE: bldr.push("out").add("inline", 1).pop(); break; default: bldr.add("out", outColl); break; } if (qi.getOffset() != 0 || qi.getFieldsObject() != null) throw new QueryException( "mapReduce does not allow the offset/retrievedFields query options."); if (qi.getQueryObject() != null) bldr.add("query", qi.getQueryObject()); if (qi.getLimit() > 0) bldr.add("limit", qi.getLimit()); if (qi.getSortObject() != null) bldr.add("sort", qi.getSortObject()); bldr.add("map", map); bldr.add("reduce", reduce); if (finalize != null && finalize.length() > 0) bldr.add("finalize", finalize); if (scopeFields != null && scopeFields.size() > 0) bldr.add("scope", mapr.toMongoObject(null, null, scopeFields)); DBObject dbObj = bldr.get(); CommandResult cr = dbColl.getDB().command(dbObj); cr.throwOnError(); MapreduceResults mrRes = (MapreduceResults) mapr.fromDBObject(MapreduceResults.class, cr, 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; } /** 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; } 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) { Entity entityAnn = getMapper().getMappedClass(clazzOrEntity).getEntityAnnotation(); if (entityAnn != null && !"".equals(entityAnn.concern())) wc = WriteConcern.valueOf(entityAnn.concern()); } return wc; } public WriteConcern getDefaultWriteConcern() { return defConcern; } public void setDefaultWriteConcern(WriteConcern wc) { defConcern = wc; } }
/** * Default encoders * * @author Uwe Schaefer, ([email protected]) * @author scotthernandez */ @SuppressWarnings({"unchecked", "rawtypes"}) public class DefaultConverters { private static final Logr log = MorphiaLoggerFactory.get(DefaultConverters.class); private List<TypeConverter> untypedTypeEncoders = new LinkedList<TypeConverter>(); private Map<Class, List<TypeConverter>> tcMap = new ConcurrentHashMap<Class, List<TypeConverter>>(); private List<Class<? extends TypeConverter>> registeredConverterClasses = new LinkedList<Class<? extends TypeConverter>>(); private Mapper mapr; public DefaultConverters() { // some converters are commented out since the pass-through converter is enabled, at the end of // the list. // Re-enable them if that changes. // addConverter(new PassthroughConverter(DBRef.class)); // Pass-through DBObject or else the MapOfValuesConverter will process it. addConverter(new PassthroughConverter(DBObject.class, BasicDBObject.class)); // Pass-through byte[] for the driver to handle addConverter(new PassthroughConverter(byte[].class)); addConverter(new EnumSetConverter()); addConverter(new EnumConverter()); addConverter(new StringConverter()); addConverter(new CharacterConverter()); addConverter(new ByteConverter()); addConverter(new BooleanConverter()); addConverter(new DoubleConverter()); addConverter(new FloatConverter()); addConverter(new LongConverter()); addConverter(new LocaleConverter()); addConverter(new ShortConverter()); addConverter(new IntegerConverter()); addConverter(new SerializedObjectConverter()); addConverter(new CharArrayConverter()); addConverter(new DateConverter()); addConverter(new KeyConverter()); addConverter(new MapOfValuesConverter(this)); addConverter(new IterableConverter(this)); addConverter(new ClassConverter()); addConverter(new ObjectIdConverter()); addConverter(new TimestampConverter()); // generic converter that will just pass things through. addConverter(new PassthroughConverter()); } /** * Add a type converter. If it is a duplicate for an existing type, it will override that type. * * @param tc */ public TypeConverter addConverter(TypeConverter tc) { if (tc.getSupportedTypes() != null) for (Class c : tc.getSupportedTypes()) addTypedConverter(c, tc); else untypedTypeEncoders.add(tc); tc.setMapper(mapr); registeredConverterClasses.add(tc.getClass()); return tc; } public TypeConverter addConverter(Class<? extends TypeConverter> clazz) { return addConverter((TypeConverter) this.mapr.getOptions().objectFactory.createInstance(clazz)); } /** * Removes the type converter. * * @param tc */ public void removeConverter(TypeConverter tc) { if (tc.getSupportedTypes() == null) untypedTypeEncoders.remove(tc); else for (List<TypeConverter> tcList : tcMap.values()) if (tcList.contains(tc)) tcList.remove(tc); registeredConverterClasses.remove(tc.getClass()); } public boolean isRegistered(Class<? extends TypeConverter> tcClass) { return registeredConverterClasses.contains(tcClass); } private void addTypedConverter(Class type, TypeConverter tc) { if (tcMap.containsKey(type)) { tcMap.get(type).add(0, tc); log.warning("Added duplicate converter for " + type + " ; " + tcMap.get(type)); } else { ArrayList<TypeConverter> vals = new ArrayList<TypeConverter>(); vals.add(tc); tcMap.put(type, vals); } } public void fromDBObject(final DBObject dbObj, final MappedField mf, final Object targetEntity) { Object object = mf.getDbObjectValue(dbObj); if (object == null) { processMissingField(mf); } else { TypeConverter enc = getEncoder(mf); Object decodedValue = enc.decode(mf.getType(), object, mf); try { mf.setFieldValue(targetEntity, decodedValue); } catch (IllegalArgumentException e) { throw new MappingException( "Error setting value from converter (" + enc.getClass().getSimpleName() + ") for " + mf.getFullName() + " to " + decodedValue); } } } protected void processMissingField(final MappedField mf) { // we ignore missing values. } private TypeConverter getEncoder(MappedField mf) { return getEncoder(null, mf); } private TypeConverter getEncoder(Object val, MappedField mf) { List<TypeConverter> tcs = null; if (val != null) tcs = tcMap.get(val.getClass()); if (tcs == null || (tcs.size() > 0 && tcs.get(0) instanceof PassthroughConverter)) tcs = tcMap.get(mf.getType()); if (tcs != null) { if (tcs.size() > 1) log.warning( "Duplicate converter for " + mf.getType() + ", returning first one from " + tcs); return tcs.get(0); } for (TypeConverter tc : untypedTypeEncoders) if (tc.canHandle(mf) || (val != null && tc.isSupported(val.getClass(), mf))) return tc; throw new ConverterNotFoundException( "Cannot find encoder for " + mf.getType() + " as need for " + mf.getFullName()); } private TypeConverter getEncoder(final Class c) { List<TypeConverter> tcs = tcMap.get(c); if (tcs != null) { if (tcs.size() > 1) log.warning("Duplicate converter for " + c + ", returning first one from " + tcs); return tcs.get(0); } for (TypeConverter tc : untypedTypeEncoders) if (tc.canHandle(c)) return tc; throw new ConverterNotFoundException("Cannot find encoder for " + c.getName()); } public void toDBObject( final Object containingObject, final MappedField mf, final DBObject dbObj, MapperOptions opts) { Object fieldValue = mf.getFieldValue(containingObject); TypeConverter enc = getEncoder(fieldValue, mf); Object encoded = enc.encode(fieldValue, mf); if (encoded != null || opts.storeNulls) { dbObj.put(mf.getNameToStore(), encoded); } } public Object decode(Class c, Object fromDBObject, MappedField mf) { if (c == null) c = fromDBObject.getClass(); return getEncoder(c).decode(c, fromDBObject, mf); } public Object decode(Class c, Object fromDBObject) { return decode(c, fromDBObject, null); } public Object encode(Object o) { return encode(o.getClass(), o); } public Object encode(Class c, Object o) { return getEncoder(c).encode(o); } public void setMapper(Mapper mapr) { this.mapr = mapr; for (List<TypeConverter> tcs : tcMap.values()) for (TypeConverter tc : tcs) tc.setMapper(mapr); for (TypeConverter tc : untypedTypeEncoders) tc.setMapper(mapr); } public boolean hasSimpleValueConverter(MappedField c) { TypeConverter conv = getEncoder(c); return (conv instanceof SimpleValueConverter); } public boolean hasSimpleValueConverter(Class c) { TypeConverter conv = getEncoder(c); return (conv instanceof SimpleValueConverter); } public boolean hasSimpleValueConverter(Object o) { if (o == null) return false; if (o instanceof Class) return hasSimpleValueConverter((Class) o); else if (o instanceof MappedField) return hasSimpleValueConverter((MappedField) o); else return hasSimpleValueConverter(o.getClass()); } public boolean hasDbObjectConverter(MappedField c) { TypeConverter conv = getEncoder(c); return conv != null && !(conv instanceof PassthroughConverter) && !(conv instanceof SimpleValueConverter); } public boolean hasDbObjectConverter(Class c) { TypeConverter conv = getEncoder(c); return conv != null && !(conv instanceof PassthroughConverter) && !(conv instanceof SimpleValueConverter); } }