/**
   * Writes the given {@link Map} using the given {@link MongoPersistentProperty} information.
   *
   * @param map must not {@literal null}.
   * @param property must not be {@literal null}.
   * @return
   */
  protected DBObject createMap(Map<Object, Object> map, MongoPersistentProperty property) {

    Assert.notNull(map, "Given map must not be null!");
    Assert.notNull(property, "PersistentProperty must not be null!");

    if (!property.isDbReference()) {
      return writeMapInternal(map, new BasicDBObject(), property.getTypeInformation());
    }

    BasicDBObject dbObject = new BasicDBObject();

    for (Map.Entry<Object, Object> entry : map.entrySet()) {

      Object key = entry.getKey();
      Object value = entry.getValue();

      if (conversions.isSimpleType(key.getClass())) {

        String simpleKey = prepareMapKey(key.toString());
        dbObject.put(simpleKey, value != null ? createDBRef(value, property) : null);

      } else {
        throw new MappingException("Cannot use a complex object as a key value.");
      }
    }

    return dbObject;
  }
    /*
     * (non-Javadoc)
     * @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
     */
    public <T> T getPropertyValue(MongoPersistentProperty property) {

      String expression = property.getSpelExpression();
      Object value = expression != null ? evaluator.evaluate(expression) : source.get(property);

      if (value == null) {
        return null;
      }

      return readValue(value, property.getTypeInformation(), path);
    }
  /**
   * Writes the given {@link Collection} using the given {@link MongoPersistentProperty}
   * information.
   *
   * @param collection must not be {@literal null}.
   * @param property must not be {@literal null}.
   * @return
   */
  protected DBObject createCollection(Collection<?> collection, MongoPersistentProperty property) {

    if (!property.isDbReference()) {
      return writeCollectionInternal(collection, property.getTypeInformation(), new BasicDBList());
    }

    BasicDBList dbList = new BasicDBList();

    for (Object element : collection) {

      if (element == null) {
        continue;
      }

      DBRef dbRef = createDBRef(element, property);
      dbList.add(dbRef);
    }

    return dbList;
  }
  protected DBRef createDBRef(Object target, MongoPersistentProperty property) {

    Assert.notNull(target);

    if (target instanceof DBRef) {
      return (DBRef) target;
    }

    MongoPersistentEntity<?> targetEntity = mappingContext.getPersistentEntity(target.getClass());
    targetEntity =
        targetEntity == null
            ? targetEntity = mappingContext.getPersistentEntity(property)
            : targetEntity;

    if (null == targetEntity) {
      throw new MappingException("No mapping metadata found for " + target.getClass());
    }

    MongoPersistentProperty idProperty = targetEntity.getIdProperty();

    if (idProperty == null) {
      throw new MappingException("No id property found on class " + targetEntity.getType());
    }

    Object id = null;

    if (target.getClass().equals(idProperty.getType())) {
      id = target;
    } else {
      PersistentPropertyAccessor accessor = targetEntity.getPropertyAccessor(target);
      id = accessor.getProperty(idProperty);
    }

    if (null == id) {
      throw new MappingException("Cannot create a reference to an object with a NULL id.");
    }

    return dbRefResolver.createDbRef(
        property == null ? null : property.getDBRef(), targetEntity, idMapper.convertId(id));
  }
  /*
   * (non-Javadoc)
   * @see org.springframework.data.mongodb.core.convert.MongoWriter#toDBRef(java.lang.Object, org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
   */
  public DBRef toDBRef(Object object, MongoPersistentProperty referingProperty) {

    org.springframework.data.mongodb.core.mapping.DBRef annotation = null;

    if (referingProperty != null) {
      annotation = referingProperty.getDBRef();
      Assert.isTrue(annotation != null, "The referenced property has to be mapped with @DBRef!");
    }

    // @see DATAMONGO-913
    if (object instanceof LazyLoadingProxy) {
      return ((LazyLoadingProxy) object).toDBRef();
    }

    return createDBRef(object, referingProperty);
  }
  @SuppressWarnings({"unchecked"})
  protected void writePropertyInternal(Object obj, DBObject dbo, MongoPersistentProperty prop) {

    if (obj == null) {
      return;
    }

    DBObjectAccessor accessor = new DBObjectAccessor(dbo);

    TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
    TypeInformation<?> type = prop.getTypeInformation();

    if (valueType.isCollectionLike()) {
      DBObject collectionInternal = createCollection(asCollection(obj), prop);
      accessor.put(prop, collectionInternal);
      return;
    }

    if (valueType.isMap()) {
      DBObject mapDbObj = createMap((Map<Object, Object>) obj, prop);
      accessor.put(prop, mapDbObj);
      return;
    }

    if (prop.isDbReference()) {

      DBRef dbRefObj = null;

      /*
       * If we already have a LazyLoadingProxy, we use it's cached DBRef value instead of
       * unnecessarily initializing it only to convert it to a DBRef a few instructions later.
       */
      if (obj instanceof LazyLoadingProxy) {
        dbRefObj = ((LazyLoadingProxy) obj).toDBRef();
      }

      dbRefObj = dbRefObj != null ? dbRefObj : createDBRef(obj, prop);

      if (null != dbRefObj) {
        accessor.put(prop, dbRefObj);
        return;
      }
    }

    /*
     * If we have a LazyLoadingProxy we make sure it is initialized first.
     */
    if (obj instanceof LazyLoadingProxy) {
      obj = ((LazyLoadingProxy) obj).getTarget();
    }

    // Lookup potential custom target type
    Class<?> basicTargetType = conversions.getCustomWriteTarget(obj.getClass(), null);

    if (basicTargetType != null) {
      accessor.put(prop, conversionService.convert(obj, basicTargetType));
      return;
    }

    Object existingValue = accessor.get(prop);
    BasicDBObject propDbObj =
        existingValue instanceof BasicDBObject
            ? (BasicDBObject) existingValue
            : new BasicDBObject();
    addCustomTypeKeyIfNecessary(ClassTypeInformation.from(prop.getRawType()), obj, propDbObj);

    MongoPersistentEntity<?> entity =
        isSubtype(prop.getType(), obj.getClass())
            ? mappingContext.getPersistentEntity(obj.getClass())
            : mappingContext.getPersistentEntity(type);

    writeInternal(obj, propDbObj, entity);
    accessor.put(prop, propDbObj);
  }
  private <S extends Object> S read(
      final MongoPersistentEntity<S> entity, final DBObject dbo, final ObjectPath path) {

    final DefaultSpELExpressionEvaluator evaluator =
        new DefaultSpELExpressionEvaluator(dbo, spELContext);

    ParameterValueProvider<MongoPersistentProperty> provider =
        getParameterProvider(entity, dbo, evaluator, path);
    EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
    S instance = instantiator.createInstance(entity, provider);

    final PersistentPropertyAccessor accessor =
        new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance), conversionService);

    final MongoPersistentProperty idProperty = entity.getIdProperty();
    final S result = instance;

    // make sure id property is set before all other properties
    Object idValue = null;

    if (idProperty != null) {
      idValue = getValueInternal(idProperty, dbo, evaluator, path);
      accessor.setProperty(idProperty, idValue);
    }

    final ObjectPath currentPath =
        path.push(result, entity, idValue != null ? dbo.get(idProperty.getFieldName()) : null);

    // Set properties not already set in the constructor
    entity.doWithProperties(
        new PropertyHandler<MongoPersistentProperty>() {
          public void doWithPersistentProperty(MongoPersistentProperty prop) {

            // we skip the id property since it was already set
            if (idProperty != null && idProperty.equals(prop)) {
              return;
            }

            if (!dbo.containsField(prop.getFieldName()) || entity.isConstructorArgument(prop)) {
              return;
            }

            accessor.setProperty(prop, getValueInternal(prop, dbo, evaluator, currentPath));
          }
        });

    // Handle associations
    entity.doWithAssociations(
        new AssociationHandler<MongoPersistentProperty>() {
          public void doWithAssociation(Association<MongoPersistentProperty> association) {

            final MongoPersistentProperty property = association.getInverse();
            Object value = dbo.get(property.getFieldName());

            if (value == null || entity.isConstructorArgument(property)) {
              return;
            }

            DBRef dbref = value instanceof DBRef ? (DBRef) value : null;

            DbRefProxyHandler handler =
                new DefaultDbRefProxyHandler(
                    spELContext, mappingContext, MappingMongoConverter.this);
            DbRefResolverCallback callback =
                new DefaultDbRefResolverCallback(
                    dbo, currentPath, evaluator, MappingMongoConverter.this);

            accessor.setProperty(
                property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
          }
        });

    return result;
  }