package org.mongodb.morphia.mapping; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import org.mongodb.morphia.Datastore; import org.mongodb.morphia.mapping.cache.EntityCache; import org.mongodb.morphia.utils.IterHelper; import org.mongodb.morphia.utils.IterHelper.MapIterCallback; import org.mongodb.morphia.utils.ReflectionUtils; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; class EmbeddedMapper implements CustomMapper { static boolean shouldSaveClassName(final Object rawVal, final Object convertedVal, final MappedField mf) { if (rawVal == null || mf == null) { return true; } if (mf.isSingleValue()) { return !(mf.getType().equals(rawVal.getClass()) && !(convertedVal instanceof BasicDBList)); } boolean isDBObject = convertedVal instanceof DBObject; boolean anInterface = mf.getSubClass().isInterface(); boolean anAbstract = Modifier.isAbstract(mf.getSubClass().getModifiers()); boolean equals = mf.getSubClass().equals(rawVal.getClass()); return convertedVal == null || !isDBObject || anInterface || anAbstract || !equals; } private static boolean isMapOrCollection(final MappedField mf) { return Map.class.isAssignableFrom(mf.getSubClass()) || Iterable.class.isAssignableFrom(mf.getSubClass()); } @SuppressWarnings("ConstantConditions") @Override public void fromDBObject(final Datastore datastore, final DBObject dbObject, final MappedField mf, final Object entity, final EntityCache cache, final Mapper mapper) { try { if (mf.isMap()) { readMap(datastore, mapper, entity, cache, mf, dbObject); } else if (mf.isMultipleValues()) { readCollection(datastore, mapper, entity, cache, mf, dbObject); } else { // single element final Object dbVal = mf.getDbObjectValue(dbObject); if (dbVal != null) { final boolean isDBObject = dbVal instanceof DBObject; //run converters if (isDBObject && !mapper.isMapped(mf.getConcreteType()) && (mapper.getConverters().hasDbObjectConverter(mf) || mapper.getConverters() .hasDbObjectConverter(mf.getType()))) { mapper.getConverters().fromDBObject(dbObject, mf, entity); } else { Object refObj; if (mapper.getConverters().hasSimpleValueConverter(mf) || mapper.getConverters() .hasSimpleValueConverter(mf.getType())) { refObj = mapper.getConverters().decode(mf.getType(), dbVal, mf); } else { DBObject value = (DBObject) dbVal; refObj = mapper.getOptions().getObjectFactory().createInstance(mapper, mf, value); refObj = mapper.fromDb(datastore, value, refObj, cache); } if (refObj != null) { mf.setFieldValue(entity, refObj); } } } } } catch (Exception e) { throw new RuntimeException(e); } } @Override public void toDBObject(final Object entity, final MappedField mf, final DBObject dbObject, final Map<Object, DBObject> involvedObjects, final Mapper mapper) { final String name = mf.getNameToStore(); final Object fieldValue = mf.getFieldValue(entity); if (mf.isMap()) { writeMap(mf, dbObject, involvedObjects, name, fieldValue, mapper); } else if (mf.isMultipleValues()) { writeCollection(mf, dbObject, involvedObjects, name, fieldValue, mapper); } else { //run converters if (mapper.getConverters().hasDbObjectConverter(mf) || mapper.getConverters().hasDbObjectConverter(entity.getClass())) { mapper.getConverters().toDBObject(entity, mf, dbObject, mapper.getOptions()); return; } final DBObject dbObj = fieldValue == null ? null : mapper.toDBObject(fieldValue, involvedObjects); if (dbObj != null) { if (!shouldSaveClassName(fieldValue, dbObj, mf)) { dbObj.removeField(Mapper.CLASS_NAME_FIELDNAME); } if (!dbObj.keySet().isEmpty() || mapper.getOptions().isStoreEmpties()) { dbObject.put(name, dbObj); } } } } @SuppressWarnings("unchecked") private void readCollection(final Datastore datastore, final Mapper mapper, final Object entity, final EntityCache cache, final MappedField mf, final DBObject dbObject) { Collection values; final Object dbVal = mf.getDbObjectValue(dbObject); if (dbVal != null) { // multiple documents in a List values = mf.isSet() ? mapper.getOptions().getObjectFactory().createSet(mf) : mapper.getOptions().getObjectFactory().createList(mf); final List dbValues; if (dbVal instanceof List) { dbValues = (List) dbVal; } else { dbValues = new BasicDBList(); dbValues.add(dbVal); } EphemeralMappedField ephemeralMappedField = !mapper.isMapped(mf.getType()) && isMapOrCollection(mf) && (mf.getSubType() instanceof ParameterizedType) ? new EphemeralMappedField((ParameterizedType) mf.getSubType(), mf, mapper) : null; for (final Object o : dbValues) { Object newEntity = null; if (o != null) { //run converters if (mapper.getConverters().hasSimpleValueConverter(mf) || mapper.getConverters() .hasSimpleValueConverter(mf.getSubClass())) { newEntity = mapper.getConverters().decode(mf.getSubClass(), o, mf); } else { newEntity = readMapOrCollectionOrEntity(datastore, mapper, cache, mf, ephemeralMappedField, (DBObject) o); } } values.add(newEntity); } if (!values.isEmpty() || mapper.getOptions().isStoreEmpties()) { if (mf.getType().isArray()) { mf.setFieldValue(entity, ReflectionUtils.convertToArray(mf.getSubClass(), ReflectionUtils.iterToList(values))); } else { mf.setFieldValue(entity, values); } } } } @SuppressWarnings("unchecked") private void readMap(final Datastore datastore, final Mapper mapper, final Object entity, final EntityCache cache, final MappedField mf, final DBObject dbObject) { final DBObject dbObj = (DBObject) mf.getDbObjectValue(dbObject); if (dbObj != null) { final Map map = mapper.getOptions().getObjectFactory().createMap(mf); final EphemeralMappedField ephemeralMappedField = isMapOrCollection(mf) ? new EphemeralMappedField((ParameterizedType) mf.getSubType(), mf, mapper) : null; new IterHelper<Object, Object>().loopMap(dbObj, new MapIterCallback<Object, Object>() { @Override public void eval(final Object k, final Object val) { Object newEntity = null; //run converters if (val != null) { if (mapper.getConverters().hasSimpleValueConverter(mf) || mapper.getConverters().hasSimpleValueConverter(mf.getSubClass())) { newEntity = mapper.getConverters().decode(mf.getSubClass(), val, mf); } else { if (val instanceof DBObject) { newEntity = readMapOrCollectionOrEntity(datastore, mapper, cache, mf, ephemeralMappedField, (DBObject) val); } else { newEntity = val; } } } final Object objKey = mapper.getConverters().decode(mf.getMapKeyClass(), k, mf); map.put(objKey, newEntity); } }); if (!map.isEmpty() || mapper.getOptions().isStoreEmpties()) { mf.setFieldValue(entity, map); } } } private Object readMapOrCollectionOrEntity(final Datastore datastore, final Mapper mapper, final EntityCache cache, final MappedField mf, final EphemeralMappedField ephemeralMappedField, final DBObject dbObj) { if (ephemeralMappedField != null) { mapper.fromDb(datastore, dbObj, ephemeralMappedField, cache); return ephemeralMappedField.getValue(); } else { final Object newEntity = mapper.getOptions().getObjectFactory().createInstance(mapper, mf, dbObj); return mapper.fromDb(datastore, dbObj, newEntity, cache); } } private void writeCollection(final MappedField mf, final DBObject dbObject, final Map<Object, DBObject> involvedObjects, final String name, final Object fieldValue, final Mapper mapper) { Iterable coll = null; if (fieldValue != null) { if (mf.isArray()) { coll = Arrays.asList((Object[]) fieldValue); } else { coll = (Iterable) fieldValue; } } if (coll != null) { final List<Object> values = new ArrayList<Object>(); for (final Object o : coll) { if (null == o) { values.add(null); } else if (mapper.getConverters().hasSimpleValueConverter(mf) || mapper.getConverters() .hasSimpleValueConverter(o.getClass())) { values.add(mapper.getConverters().encode(o)); } else { final Object val; if (Collection.class.isAssignableFrom(o.getClass()) || Map.class.isAssignableFrom(o.getClass())) { val = mapper.toMongoObject(o, true); } else { val = mapper.toDBObject(o, involvedObjects); } if (!shouldSaveClassName(o, val, mf)) { ((DBObject) val).removeField(Mapper.CLASS_NAME_FIELDNAME); } values.add(val); } } if (!values.isEmpty() || mapper.getOptions().isStoreEmpties()) { dbObject.put(name, values); } } } @SuppressWarnings("unchecked") private void writeMap(final MappedField mf, final DBObject dbObject, final Map<Object, DBObject> involvedObjects, final String name, final Object fieldValue, final Mapper mapper) { final Map<String, Object> map = (Map<String, Object>) fieldValue; if (map != null) { final BasicDBObject values = new BasicDBObject(); for (final Map.Entry<String, Object> entry : map.entrySet()) { final Object entryVal = entry.getValue(); final Object val; if (entryVal == null) { val = null; } else if (mapper.getConverters().hasSimpleValueConverter(mf) || mapper.getConverters().hasSimpleValueConverter(entryVal.getClass())) { val = mapper.getConverters().encode(entryVal); } else { if (Map.class.isAssignableFrom(entryVal.getClass()) || Collection.class.isAssignableFrom(entryVal.getClass())) { val = mapper.toMongoObject(entryVal, true); } else { val = mapper.toDBObject(entryVal, involvedObjects); } if (!shouldSaveClassName(entryVal, val, mf)) { if (val instanceof List) { if (((List) val).get(0) instanceof DBObject) { List<DBObject> list = (List<DBObject>) val; for (DBObject o : list) { o.removeField(Mapper.CLASS_NAME_FIELDNAME); } } } else { ((DBObject) val).removeField(Mapper.CLASS_NAME_FIELDNAME); } } } final String strKey = mapper.getConverters().encode(entry.getKey()).toString(); values.put(strKey, val); } if (!values.isEmpty() || mapper.getOptions().isStoreEmpties()) { dbObject.put(name, values); } } } }