@Override
  public Object invokeMethod(String name, Object obj) {
    Object[] args = obj.getClass().isArray() ? (Object[]) obj : new Object[] {obj};

    if (isCriteriaConstructionMethod(name, args)) {

      initializeQuery();

      uniqueResult = false;

      invokeClosureNode(args[0]);

      Object result;
      if (!uniqueResult) {
        result = query.list();
      } else {
        result = query.singleResult();
      }
      query = null;
      return result;
    }

    MetaMethod metaMethod = getMetaClass().getMetaMethod(name, args);
    if (metaMethod != null) {
      return metaMethod.invoke(this, args);
    }

    metaMethod = queryMetaClass.getMetaMethod(name, args);
    if (metaMethod != null) {
      return metaMethod.invoke(query, args);
    }

    if (args.length == 1 && args[0] instanceof Closure) {

      final PersistentProperty property = persistentEntity.getPropertyByName(name);
      if (property instanceof Association) {
        Association association = (Association) property;
        Query previousQuery = query;
        PersistentEntity previousEntity = persistentEntity;

        Query associationQuery = null;
        try {
          associationQuery = query.createQuery(property.getName());
          if (associationQuery instanceof AssociationQuery) {
            previousQuery.add((Query.Criterion) associationQuery);
          }
          query = associationQuery;
          persistentEntity = association.getAssociatedEntity();
          invokeClosureNode(args[0]);
          return query;
        } finally {

          persistentEntity = previousEntity;
          query = previousQuery;
        }
      }
    }

    throw new MissingMethodException(name, getClass(), args);
  }
  @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);
    }
  }
 private void configureAssociation(
     GrailsDomainClassProperty grailsDomainClassProperty, final Association association) {
   association.setAssociatedEntity(
       getMappingContext()
           .addPersistentEntity(grailsDomainClassProperty.getReferencedPropertyType()));
   association.setOwningSide(grailsDomainClassProperty.isOwningSide());
   String referencedPropertyName = grailsDomainClassProperty.getReferencedPropertyName();
   if (referencedPropertyName != null) {
     association.setReferencedPropertyName(referencedPropertyName);
   } else {
     GrailsDomainClassProperty otherSide = grailsDomainClassProperty.getOtherSide();
     if (otherSide != null) {
       association.setReferencedPropertyName(otherSide.getName());
     }
   }
 }
    public void index(final Object primaryKey, final List foreignKeys) {
      // if the association is a unidirectional one-to-many we store the keys
      // embedded in the owning entity, otherwise we use a foreign key
      if (!association.isBidirectional()) {
        mongoTemplate.execute(
            new DbCallback<Object>() {
              public Object doInDB(DB db) throws MongoException, DataAccessException {
                List dbRefs = new ArrayList();
                for (Object foreignKey : foreignKeys) {
                  if (isReference) {
                    dbRefs.add(
                        new DBRef(
                            db, getCollectionName(association.getAssociatedEntity()), foreignKey));
                  } else {
                    dbRefs.add(foreignKey);
                  }
                }
                nativeEntry.put(association.getName(), dbRefs);

                if (primaryKey != null) {
                  final DBCollection collection =
                      db.getCollection(getCollectionName(association.getOwner()));
                  DBObject query = new BasicDBObject(MONGO_ID_FIELD, primaryKey);
                  collection.update(query, nativeEntry);
                }
                return null;
              }
            });
      }
    }
 private boolean isReference(Association association) {
   PropertyMapping mapping = association.getMapping();
   if (mapping != null) {
     MongoAttribute attribute = (MongoAttribute) mapping.getMappedForm();
     if (attribute != null) {
       return attribute.isReference();
     }
   }
   return true;
 }
 @Override
 protected Object formulateDatabaseReference(
     PersistentEntity persistentEntity, Association association, Serializable associationId) {
   DB db = (DB) session.getNativeInterface();
   boolean isReference = isReference(association);
   if (isReference) {
     return new DBRef(db, getCollectionName(association.getAssociatedEntity()), associationId);
   }
   return associationId;
 }
 @Override
 protected void setEmbeddedCollectionKeys(
     Association association,
     EntityAccess embeddedEntityAccess,
     DBObject embeddedEntry,
     List<Serializable> keys) {
   List dbRefs = new ArrayList();
   boolean reference = isReference(association);
   for (Object foreignKey : keys) {
     if (reference) {
       dbRefs.add(
           new DBRef(
               (DB) session.getNativeInterface(),
               getCollectionName(association.getAssociatedEntity()),
               foreignKey));
     } else {
       dbRefs.add(foreignKey);
     }
   }
   embeddedEntry.put(association.getName(), dbRefs);
 }
  @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);
  }
 public List query(Object primaryKey) {
   // for a unidirectional one-to-many we use the embedded keys
   if (!association.isBidirectional()) {
     final Object indexed = nativeEntry.get(association.getName());
     if (indexed instanceof Collection) {
       if (indexed instanceof List) return (List) indexed;
       return new ArrayList((Collection) indexed);
     }
     return Collections.emptyList();
   }
   // for a bidirectional one-to-many we use the foreign key to query the inverse side of the
   // association
   Association inverseSide = association.getInverseSide();
   Query query = session.createQuery(association.getAssociatedEntity().getJavaClass());
   query.eq(inverseSide.getName(), primaryKey);
   query.projections().id();
   return query.list();
 }
  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() + ")");
        }
      }
    }
  }
 public PersistentEntity getIndexedEntity() {
   return association.getAssociatedEntity();
 }
  @Override
  public void run() {

    Map<String, Object> params = new LinkedHashMap<String, Object>(2);
    final Object parentId = getEntityAccess().getIdentifier();
    params.put(CypherBuilder.START, parentId);
    params.put(CypherBuilder.END, targetIdentifiers);

    final GraphPersistentEntity graphParent = (GraphPersistentEntity) getEntity();
    final GraphPersistentEntity graphChild =
        (GraphPersistentEntity) association.getAssociatedEntity();
    final boolean nativeParent = graphParent.getIdGenerator() == null;
    String labelsFrom = graphParent.getLabelsAsString();
    String labelsTo = graphChild.getLabelsAsString();

    final String relMatch;

    if (association instanceof DynamicToOneAssociation) {
      LinkedHashMap<String, String> attrs = new LinkedHashMap<String, String>();
      attrs.put(SOURCE_TYPE, graphParent.getJavaClass().getSimpleName());
      attrs.put(TARGET_TYPE, graphChild.getJavaClass().getSimpleName());
      relMatch = Neo4jQuery.matchForAssociation(association, "r", attrs);
    } else {
      relMatch = Neo4jQuery.matchForAssociation(association, "r");
    }

    boolean reversed = RelationshipUtils.useReversedMappingFor(association);
    if (!reversed && (association instanceof ToOne) && isUpdate) {
      // delete any previous

      String cypher;
      if (nativeParent) {
        cypher = String.format(CYPHER_DELETE_NATIVE_RELATIONSHIP, labelsFrom, relMatch);
      } else {
        cypher = String.format(CYPHER_DELETE_RELATIONSHIP, labelsFrom, relMatch);
      }

      if (log.isDebugEnabled()) {
        log.debug("DELETE Cypher [{}] for parameters [{}]", cypher, params);
      }
      graphDatabaseService.execute(cypher, Collections.singletonMap(CypherBuilder.START, parentId));
    }

    StringBuilder cypherQuery =
        new StringBuilder("MATCH (from")
            .append(labelsFrom)
            .append("), (to")
            .append(labelsTo)
            .append(") WHERE ");

    if (nativeParent) {
      cypherQuery.append("ID(from) = {start}");
    } else {
      cypherQuery.append("from.").append(CypherBuilder.IDENTIFIER).append(" = {start}");
    }
    cypherQuery.append(" AND ");
    if (graphChild.getIdGenerator() == null) {
      cypherQuery.append(" ID(to) IN {end} ");
    } else {
      cypherQuery.append("to.").append(CypherBuilder.IDENTIFIER).append(" IN {end}");
    }
    cypherQuery.append(" MERGE (from)").append(relMatch).append("(to)");

    String cypher = cypherQuery.toString();

    if (log.isDebugEnabled()) {
      log.debug("MERGE Cypher [{}] for parameters [{}]", cypher, params);
    }
    graphDatabaseService.execute(cypher, params);
  }