private Object unmarshall(PersistentEntity persistentEntity, Long id, Map<String, Object> data) {

    log.debug("unmarshalling entity {}, props {}, {}", id, data);
    EntityAccess entityAccess = new EntityAccess(persistentEntity, persistentEntity.newInstance());
    entityAccess.setConversionService(persistentEntity.getMappingContext().getConversionService());
    entityAccess.setIdentifier(id);
    data.remove("__id__");

    for (PersistentProperty property :
        entityAccess.getPersistentEntity().getPersistentProperties()) {

      String propertyName = property.getName();
      if (property instanceof Simple) { // implicitly sets version property as well
        entityAccess.setProperty(propertyName, data.remove(propertyName));
        //            } else if (property instanceof OneToOne) {
        //                log.error("property " + property.getName() + " is of type " +
        // property.getClass().getSuperclass());
      } else if (property instanceof ToOne) {
        ToOne to = (ToOne) property;

        CypherResult cypherResult =
            getSession()
                .getNativeInterface()
                .execute(
                    CypherBuilder.findRelationshipEndpointIdsFor(to),
                    Collections.singletonMap("id", id));

        Map<String, Object> row = IteratorUtil.singleOrNull(cypherResult);
        if (row != null) {
          Long endpointId = (Long) row.get("id");
          entityAccess.setProperty(
              propertyName,
              getMappingContext()
                  .getProxyFactory()
                  .createProxy(session, to.getAssociatedEntity().getJavaClass(), endpointId));
        }
      } else if ((property instanceof OneToMany) || (property instanceof ManyToMany)) {

        LazyEnititySet lazyEnititySet =
            new LazyEnititySet(
                entityAccess,
                (Association) property,
                getMappingContext().getProxyFactory(),
                getSession());
        entityAccess.setProperty(propertyName, lazyEnititySet);

      } else {
        throw new IllegalArgumentException(
            "property $property.name is of type ${property.class.superclass}");
      }
    }

    if (!data.isEmpty()) {
      GroovyObject go = (GroovyObject) (entityAccess.getEntity());
      go.setProperty(Neo4jGormEnhancer.UNDECLARED_PROPERTIES, data);
    }

    firePostLoadEvent(entityAccess.getPersistentEntity(), entityAccess);
    return entityAccess.getEntity();
  }
  @Override
  protected void deleteEntity(PersistentEntity pe, Object obj) {
    EntityAccess entityAccess = createEntityAccess(pe, obj);
    if (cancelDelete(pe, entityAccess)) {
      return;
    }

    for (Association association : pe.getAssociations()) {
      if (association.isOwningSide() && association.doesCascade(CascadeType.REMOVE)) {
        log.debug("cascading delete for property " + association.getName());

        GraphPersistentEntity otherPersistentEntity =
            (GraphPersistentEntity) association.getAssociatedEntity();
        Object otherSideValue = entityAccess.getProperty(association.getName());
        if (association instanceof ToOne) {
          deleteEntity(otherPersistentEntity, otherSideValue);
        } else {
          deleteEntities(otherPersistentEntity, (Iterable) otherSideValue);
        }
      }
    }

    getCypherEngine()
        .execute(
            String.format(
                "MATCH (n:%s) WHERE n.__id__={id} OPTIONAL MATCH (n)-[r]-() DELETE r,n",
                ((GraphPersistentEntity) pe).getLabel()),
            Collections.singletonMap("id", entityAccess.getIdentifier()));

    firePostDeleteEvent(pe, entityAccess);
  }
  @Override
  protected void setManyToMany(
      PersistentEntity persistentEntity,
      Object obj,
      DBObject nativeEntry,
      ManyToMany manyToMany,
      Collection associatedObjects,
      Map<Association, List<Serializable>> toManyKeys) {

    List ids = new ArrayList();
    if (associatedObjects != null) {
      for (Object o : associatedObjects) {
        if (o == null) {
          ids.add(null);
        } else {
          PersistentEntity childPersistentEntity =
              getMappingContext().getPersistentEntity(o.getClass().getName());
          EntityAccess entityAccess = createEntityAccess(childPersistentEntity, o);
          ids.add(entityAccess.getIdentifier());
        }
      }
    }

    nativeEntry.put(manyToMany.getName() + "_$$manyToManyIds", ids);
  }
 protected Object getCurrentVersion(final EntityAccess ea) {
   Object currentVersion = ea.getProperty("version");
   if (Number.class.isAssignableFrom(ea.getPropertyType("version"))) {
     currentVersion =
         currentVersion != null ? ((Number) currentVersion).longValue() : currentVersion;
   }
   return currentVersion;
 }
  RelationshipPendingInsert(
      EntityAccess source, String relType, EntityAccess target, CypherEngine cypherEngine) {
    super(source.getPersistentEntity(), -1l, source.getEntity(), source);
    this.relType = relType;
    this.target = target;
    this.cypherEngine = cypherEngine;

    //        validateNonExistingRelationship("constructor");

  }
  @Override
  protected void deleteEntities(
      PersistentEntity pe, @SuppressWarnings("rawtypes") Iterable objects) {
    List<EntityAccess> entityAccesses = new ArrayList<EntityAccess>();
    List<Object> ids = new ArrayList<Object>();
    Map<PersistentEntity, Collection<Object>> cascades =
        new HashMap<PersistentEntity, Collection<Object>>();

    for (Object obj : objects) {
      EntityAccess entityAccess = createEntityAccess(pe, obj);
      if (cancelDelete(pe, entityAccess)) {
        return;
      }
      entityAccesses.add(entityAccess);
      ids.add(entityAccess.getIdentifier());

      // populate cascades
      for (Association association : pe.getAssociations()) {
        Object property = entityAccess.getProperty(association.getName());
        if (association.isOwningSide()
            && association.doesCascade(CascadeType.REMOVE)
            && (property != null)) {

          PersistentEntity associatedEntity = association.getAssociatedEntity();

          Collection<Object> cascadesForPersistentEntity = cascades.get(associatedEntity);
          if (cascadesForPersistentEntity == null) {
            cascadesForPersistentEntity = new ArrayList<Object>();
            cascades.put(associatedEntity, cascadesForPersistentEntity);
          }

          if (association instanceof ToOne) {
            cascadesForPersistentEntity.add(property);
          } else {
            cascadesForPersistentEntity.addAll((Collection<?>) property);
          }
        }
      }
    }

    for (Map.Entry<PersistentEntity, Collection<Object>> entry : cascades.entrySet()) {
      deleteEntities(entry.getKey(), entry.getValue());
    }

    getCypherEngine()
        .execute(
            String.format(
                "MATCH (n:%s) WHERE n.__id__ in {ids} OPTIONAL MATCH (n)-[r]-() DELETE r,n",
                ((GraphPersistentEntity) pe).getLabel()),
            Collections.singletonMap("ids", ids));

    for (EntityAccess entityAccess : entityAccesses) {
      firePostDeleteEvent(pe, entityAccess);
    }
  }
  public RelationshipPendingInsert(
      EntityAccess parent,
      Association association,
      Collection<Serializable> pendingInserts,
      GraphDatabaseService graphDatabaseService,
      boolean isUpdate) {
    super(parent.getPersistentEntity(), -1L, parent.getEntity(), parent);

    this.graphDatabaseService = graphDatabaseService;
    this.targetIdentifiers = pendingInserts;
    this.association = association;
    this.isUpdate = isUpdate;
  }
  @Override
  protected void loadEmbeddedCollection(
      EmbeddedCollection embeddedCollection,
      EntityAccess ea,
      Object embeddedInstances,
      String propertyKey) {

    Collection<Object> instances;
    if (List.class.isAssignableFrom(embeddedCollection.getType())) {
      instances = new ArrayList<Object>();
    } else {
      instances = new HashSet<Object>();
    }

    if (embeddedInstances instanceof List) {
      List list = (List) embeddedInstances;
      for (Object dbo : list) {
        if (dbo instanceof BasicDBObject) {
          BasicDBObject nativeEntry = (BasicDBObject) dbo;
          Object instance =
              createObjectFromEmbeddedNativeEntry(
                  embeddedCollection.getAssociatedEntity(), nativeEntry);
          instances.add(instance);
        }
      }
    }

    ea.setProperty(embeddedCollection.getName(), instances);
  }
  @Override
  public void run() {

    //        validateNonExistingRelationship("execution");

    List params = new ArrayList();
    params.add(getEntityAccess().getIdentifier());
    params.add(target.getIdentifier());

    String labelsFrom = ((GraphPersistentEntity) getEntity()).getLabelsAsString();
    String labelsTo = ((GraphPersistentEntity) target.getPersistentEntity()).getLabelsAsString();
    String cypher =
        String.format(
            "MATCH (from%s {__id__:{1}}), (to%s {__id__:{2}}) MERGE (from)-[:%s]->(to)",
            labelsFrom, labelsTo, relType);
    cypherEngine.execute(cypher, params);
  }
  @Override
  public boolean isDirty(Object instance, Object entry) {
    if (super.isDirty(instance, entry)) {
      return true;
    }

    DBObject dbo = (DBObject) entry;
    PersistentEntity entity = getPersistentEntity();

    EntityAccess entityAccess = createEntityAccess(entity, instance, dbo);

    DBObject cached =
        (DBObject)
            ((SessionImplementor<?>) getSession())
                .getCachedEntry(entity, (Serializable) entityAccess.getIdentifier(), true);

    return !dbo.equals(cached);
  }
  @Override
  protected Serializable persistEntity(PersistentEntity pe, Object obj) {
    if ((obj == null) || (getSession().containsPersistingInstance(obj))) {
      return null;
    }

    EntityAccess entityAccess = createEntityAccess(pe, obj);
    if (getMappingContext().getProxyFactory().isProxy(obj)) {
      return (Serializable) entityAccess.getIdentifier();
    }

    getSession().addPersistingInstance(obj);

    // cancel operation if vetoed
    boolean isUpdate = entityAccess.getIdentifier() != null;
    if (isUpdate) {
      if (cancelUpdate(pe, entityAccess)) {
        return null;
      }
      getSession()
          .addPendingUpdate(
              new NodePendingUpdate(entityAccess, getCypherEngine(), getMappingContext()));
      persistAssociationsOfEntity(pe, entityAccess, true);
      firePostUpdateEvent(pe, entityAccess);

    } else {
      if (cancelInsert(pe, entityAccess)) {
        return null;
      }
      getSession()
          .addPendingInsert(
              new NodePendingInsert(
                  getSession().getDatastore().nextIdForType(pe),
                  entityAccess,
                  getCypherEngine(),
                  getMappingContext()));
      persistAssociationsOfEntity(pe, entityAccess, false);
      firePostInsertEvent(pe, entityAccess);
    }

    return (Serializable) entityAccess.getIdentifier();
  }
  private void persistAssociationsOfEntity(
      PersistentEntity pe, EntityAccess entityAccess, boolean isUpdate) {

    Object obj = entityAccess.getEntity();
    DirtyCheckable dirtyCheckable = null;
    if (obj instanceof DirtyCheckable) {
      dirtyCheckable = (DirtyCheckable) obj;
    }

    for (PersistentProperty pp : pe.getAssociations()) {
      if ((!isUpdate) || ((dirtyCheckable != null) && dirtyCheckable.hasChanged(pp.getName()))) {

        Object propertyValue = entityAccess.getProperty(pp.getName());

        if ((pp instanceof OneToMany) || (pp instanceof ManyToMany)) {
          Association association = (Association) pp;

          if (propertyValue != null) {

            if (association.isBidirectional()) { // Populate other side of bidi
              for (Object associatedObject : (Iterable) propertyValue) {
                EntityAccess assocEntityAccess =
                    createEntityAccess(association.getAssociatedEntity(), associatedObject);
                assocEntityAccess.setProperty(association.getReferencedPropertyName(), obj);
              }
            }

            Iterable targets = (Iterable) propertyValue;
            persistEntities(association.getAssociatedEntity(), targets);

            boolean reversed = RelationshipUtils.useReversedMappingFor(association);

            if (!reversed) {
              if (!(propertyValue instanceof LazyEnititySet)) {
                LazyEnititySet les =
                    new LazyEnititySet(
                        entityAccess,
                        association,
                        getMappingContext().getProxyFactory(),
                        getSession());
                les.addAll(targets);
                entityAccess.setProperty(association.getName(), les);
              }
            }
          }
        } else if (pp instanceof ToOne) {
          if (propertyValue != null) {
            ToOne to = (ToOne) pp;

            if (to.isBidirectional()) { // Populate other side of bidi
              EntityAccess assocEntityAccess =
                  createEntityAccess(to.getAssociatedEntity(), propertyValue);
              if (to instanceof OneToOne) {
                assocEntityAccess.setProperty(to.getReferencedPropertyName(), obj);
              } else {
                Collection collection =
                    (Collection) assocEntityAccess.getProperty(to.getReferencedPropertyName());
                if (collection == null) {
                  collection = new ArrayList();
                  assocEntityAccess.setProperty(to.getReferencedPropertyName(), collection);
                }
                if (!collection.contains(obj)) {
                  collection.add(obj);
                }
              }
            }

            persistEntity(to.getAssociatedEntity(), propertyValue);

            boolean reversed = RelationshipUtils.useReversedMappingFor(to);
            String relType = RelationshipUtils.relationshipTypeUsedFor(to);

            if (!reversed) {
              getSession()
                  .addPendingInsert(
                      new RelationshipPendingInsert(
                          entityAccess,
                          relType,
                          new EntityAccess(to.getAssociatedEntity(), propertyValue),
                          getCypherEngine()));
            }
          }
        } else {
          throw new IllegalArgumentException(
              "wtf don't know how to handle " + pp + "(" + pp.getClass() + ")");
        }
      }
    }
  }