public static void relationshipModified(
      final Principal user,
      final RelationshipInterface relationship,
      final PropertyKey key,
      final Object previousValue,
      final Object newValue) {

    TransactionCommand command = currentCommand.get();
    if (command != null) {

      ModificationQueue modificationQueue = command.getModificationQueue();
      if (modificationQueue != null) {

        modificationQueue.modify(user, relationship, key, previousValue, newValue);

      } else {

        logger.log(Level.SEVERE, "Got empty changeSet from command!");
      }

    } else {

      logger.log(Level.SEVERE, "Relationship deleted while outside of transaction!");
    }
  }
  public Collection<ModificationEvent> getModificationEvents() {

    ModificationQueue modificationQueue = queues.get();
    if (modificationQueue != null) {

      return modificationQueue.getModificationEvents();
    }

    return null;
  }
  public static boolean isDeleted(final Relationship rel) {

    if (!inTransaction()) {
      throw new NotInTransactionException("Not in transaction.");
    }

    final ModificationQueue queue = queues.get();
    if (queue != null) {
      return queue.isDeleted(rel);
    }

    return false;
  }
  public static void nodeDeleted(final Principal user, final NodeInterface node) {

    TransactionCommand command = currentCommand.get();
    if (command != null) {

      ModificationQueue modificationQueue = command.getModificationQueue();
      if (modificationQueue != null) {

        modificationQueue.delete(user, node);

      } else {

        logger.log(Level.SEVERE, "Got empty changeSet from command!");
      }

    } else {

      logger.log(Level.SEVERE, "Node deleted while outside of transaction!");
    }
  }
  public static void relationshipDeleted(
      final Principal user, final RelationshipInterface relationship, final boolean passive) {

    TransactionCommand command = currentCommand.get();
    if (command != null) {

      ModificationQueue modificationQueue = command.getModificationQueue();
      if (modificationQueue != null) {

        modificationQueue.delete(user, relationship, passive);

      } else {

        logger.log(Level.SEVERE, "Got empty changeSet from command!");
      }

    } else {

      logger.log(Level.SEVERE, "Relationship deleted while outside of transaction!");
    }
  }
  public static void postProcess(final String key, final TransactionPostProcess process) {

    TransactionCommand command = currentCommand.get();
    if (command != null) {

      ModificationQueue modificationQueue = command.getModificationQueue();
      if (modificationQueue != null) {

        modificationQueue.postProcess(key, process);

      } else {

        logger.log(Level.SEVERE, "Got empty changeSet from command!");
      }

    } else {

      logger.log(
          Level.SEVERE,
          "Trying to register transaction post processing while outside of transaction!");
    }
  }
  public ModificationQueue finishTx() {

    final TransactionReference tx = transactions.get();
    ModificationQueue modificationQueue = null;

    if (tx != null) {

      if (tx.isToplevel()) {

        modificationQueue = queues.get();

        final Set<String> synchronizationKeys = modificationQueue.getSynchronizationKeys();

        // cleanup
        queues.remove();
        buffers.remove();
        currentCommand.remove();
        transactions.remove();

        try {
          tx.close();

        } catch (Throwable t) {
          t.printStackTrace();

        } finally {

          // release semaphores as the transaction is now finished
          semaphore.release(synchronizationKeys); // careful: this can be null
        }

      } else {

        tx.end();
      }
    }

    return modificationQueue;
  }
  public void commitTx(final boolean doValidation) throws FrameworkException {

    final TransactionReference tx = transactions.get();
    if (tx != null && tx.isToplevel()) {

      final ModificationQueue modificationQueue = queues.get();
      final ErrorBuffer errorBuffer = buffers.get();

      // 0.5: let transaction listeners examine (and prevent?) commit
      for (final StructrTransactionListener listener : listeners) {
        listener.beforeCommit(
            securityContext, modificationQueue.getModificationEvents(), tx.getSource());
      }

      // 1. do inner callbacks (may cause transaction to fail)
      if (doValidation) {

        if (!modificationQueue.doInnerCallbacks(securityContext, errorBuffer)) {

          tx.failure();
          throw new FrameworkException(
              422, "Unable to commit transaction, validation failed", errorBuffer);
        }

        // 1.5: execute validatable post-transaction action
        if (!modificationQueue.doPostProcessing(securityContext, errorBuffer)) {

          tx.failure();
          throw new FrameworkException(
              422, "Unable to commit transaction, transaction post processing failed", errorBuffer);
        }
      }

      // 2. fetch all types of entities modified in this tx
      Set<String> synchronizationKeys = modificationQueue.getSynchronizationKeys();

      // we need to protect the validation and indexing part of every transaction
      // from being entered multiple times in the presence of validators
      // 3. acquire semaphores for each modified type
      try {
        semaphore.acquire(synchronizationKeys);
      } catch (InterruptedException iex) {
        return;
      }

      // finally, do validation under the protection of the semaphores for each type
      if (!modificationQueue.doValidation(securityContext, errorBuffer, doValidation)) {

        tx.failure();

        // create error
        throw new FrameworkException(
            422, "Unable to commit transaction, validation failed", errorBuffer);
      }

      try {
        tx.success();

      } catch (Throwable t) {
        t.printStackTrace();
      }
    }
  }
  /**
   * Call beforeModification/Creation/Deletion methods.
   *
   * @param modificationQueue
   * @param securityContext
   * @param errorBuffer
   * @return valid
   * @throws FrameworkException
   */
  public boolean doInnerCallback(
      ModificationQueue modificationQueue, SecurityContext securityContext, ErrorBuffer errorBuffer)
      throws FrameworkException {

    boolean valid = true;

    // check for modification propagation along the relationships
    if ((status & STATE_PROPAGATING_MODIFICATION) == STATE_PROPAGATING_MODIFICATION
        && object instanceof AbstractNode) {

      Set<AbstractNode> nodes = ((AbstractNode) object).getNodesForModificationPropagation();
      if (nodes != null) {

        for (AbstractNode node : nodes) {

          modificationQueue.propagatedModification(node);
        }
      }
    }

    // examine only the last 4 bits here
    switch (status & 0x000f) {
      case 15:
      case 14:
      case 13:
      case 12:
      case 11:
      case 10:
      case 9:
      case 8: // since all values >= 8 mean that the object was passively deleted, no action has to
        // be taken
        // (no callback for passive deletion!)
        break;

      case 7: // created, modified, deleted, poor guy => no callback
        break;

      case 6: // created, modified => only creation callback will be called
        valid &= object.onCreation(securityContext, errorBuffer);
        break;

      case 5: // created, deleted => no callback
        break;

      case 4: // created => creation callback
        valid &= object.onCreation(securityContext, errorBuffer);
        break;

      case 3: // modified, deleted => deletion callback
        valid &= object.onDeletion(securityContext, errorBuffer, removedProperties);
        break;

      case 2: // modified => modification callback
        valid &= object.onModification(securityContext, errorBuffer);
        break;

      case 1: // deleted => deletion callback
        valid &= object.onDeletion(securityContext, errorBuffer, removedProperties);
        break;

      case 0: // no action, no callback
        break;

      default:
        break;
    }

    // mark as finished
    modified = false;

    return valid;
  }