org.structr.core.EntityContext.java Source code

Java tutorial

Introduction

Here is the source code for org.structr.core.EntityContext.java

Source

/*
 *  Copyright (C) 2010-2013 Axel Morgner
 *
 *  This file is part of structr <http://structr.org>.
 *
 *  structr is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  structr is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with structr.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.structr.core;

import org.structr.core.graph.NewIndexNodeCommand;
import org.structr.core.graph.NodeService;
import org.structr.core.graph.IndexRelationshipCommand;
import java.lang.reflect.Field;
import org.apache.commons.lang.StringUtils;

import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.event.PropertyEntry;
import org.neo4j.graphdb.event.TransactionData;
import org.neo4j.graphdb.event.TransactionEventHandler;

import org.structr.common.CaseHelper;
import org.structr.core.property.PropertyKey;
import org.structr.common.SecurityContext;
import org.structr.common.error.ErrorBuffer;
import org.structr.common.error.FrameworkException;
import org.structr.core.entity.AbstractNode;
import org.structr.core.entity.AbstractRelationship;
import org.structr.core.entity.RelationshipMapping;
import org.structr.core.graph.NodeFactory;
import org.structr.core.graph.RelationshipFactory;

//~--- JDK imports ------------------------------------------------------------

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.structr.common.*;
import org.structr.core.property.GenericProperty;
import org.structr.core.property.PropertyMap;
import org.structr.core.module.ModuleService;
import org.structr.core.graph.NodeService.NodeIndex;
import org.structr.core.graph.NodeService.RelationshipIndex;

//~--- classes ----------------------------------------------------------------

/**
 * A global context for functional mappings between nodes and relationships,
 * property views and property validators.
 *
 * @author Axel Morgner
 * @author Christian Morgner
 */
public class EntityContext {

    private static final String COMBINED_RELATIONSHIP_KEY_SEP = " ";
    private static final Logger logger = Logger.getLogger(EntityContext.class.getName());
    //   private static final Map<Class, Set<PropertyKey>> globalWriteOncePropertyMap                  = new LinkedHashMap<Class, Set<PropertyKey>>();
    private static final Map<Class, Map<PropertyKey, Set<PropertyValidator>>> globalValidatorMap = new LinkedHashMap<Class, Map<PropertyKey, Set<PropertyValidator>>>();
    private static final Map<Class, Map<String, Set<PropertyKey>>> globalSearchablePropertyMap = new LinkedHashMap<Class, Map<String, Set<PropertyKey>>>();

    // This map contains a mapping from (sourceType, destType) -> Relation
    private static final Map<Class, Map<String, Set<PropertyKey>>> globalPropertyViewMap = new LinkedHashMap<Class, Map<String, Set<PropertyKey>>>();
    private static final Map<Class, Map<String, PropertyKey>> globalClassDBNamePropertyMap = new LinkedHashMap<Class, Map<String, PropertyKey>>();
    private static final Map<Class, Map<String, PropertyKey>> globalClassJSNamePropertyMap = new LinkedHashMap<Class, Map<String, PropertyKey>>();

    // This map contains a mapping from (sourceType, propertyKey) -> Relation
    private static final Map<Class, Map<String, PropertyGroup>> globalAggregatedPropertyGroupMap = new LinkedHashMap<Class, Map<String, PropertyGroup>>();
    private static final Map<Class, Map<String, PropertyGroup>> globalPropertyGroupMap = new LinkedHashMap<Class, Map<String, PropertyGroup>>();

    // This map contains view-dependent result set transformations
    private static final Map<Class, Map<String, ViewTransformation>> viewTransformations = new LinkedHashMap<Class, Map<String, ViewTransformation>>();

    // This set contains all known properties
    private static final Set<PropertyKey> globalKnownPropertyKeys = new LinkedHashSet<PropertyKey>();
    private static final Map<Class, Set<Transformation<GraphObject>>> globalEntityCreationTransformationMap = new LinkedHashMap<Class, Set<Transformation<GraphObject>>>();
    private static final Map<String, String> normalizedEntityNameCache = new LinkedHashMap<String, String>();
    private static final Set<StructrTransactionListener> transactionListeners = new LinkedHashSet<StructrTransactionListener>();
    private static final Map<String, RelationshipMapping> globalRelationshipNameMap = new LinkedHashMap<String, RelationshipMapping>();
    private static final Map<String, Class> globalRelationshipClassMap = new LinkedHashMap<String, Class>();
    private static final EntityContextModificationListener globalModificationListener = new EntityContextModificationListener();
    private static final Map<Long, FrameworkException> exceptionMap = new LinkedHashMap<Long, FrameworkException>();
    private static final Map<Class, Set<Class>> interfaceMap = new LinkedHashMap<Class, Set<Class>>();
    private static final Map<String, Class> reverseInterfaceMap = new LinkedHashMap<String, Class>();
    private static Map<String, Class> cachedEntities = new LinkedHashMap<String, Class>();

    private static final Map<Long, TransactionChangeSet> globalChangeSets = new ConcurrentHashMap<Long, TransactionChangeSet>();
    private static final ThreadLocal<SecurityContext> securityContextMap = new ThreadLocal<SecurityContext>();
    private static final ThreadLocal<Long> transactionKeyMap = new ThreadLocal<Long>();
    private static GenericFactory genericFactory = new DefaultGenericFactory();

    //~--- methods --------------------------------------------------------

    /**
     * Initialize the entity context for the given class.
     * This method sets defaults common for any class, f.e. registers any of its parent properties.
     *
     * @param type
     */
    public static void init(Class type) {

        // 1. Register searchable keys of superclasses
        for (Enum index : NodeService.NodeIndex.values()) {

            String indexName = index.name();
            Map<String, Set<PropertyKey>> searchablePropertyMapForType = getSearchablePropertyMapForType(type);
            Set<PropertyKey> searchablePropertySet = searchablePropertyMapForType.get(indexName);

            if (searchablePropertySet == null) {

                searchablePropertySet = new LinkedHashSet<PropertyKey>();

                searchablePropertyMapForType.put(indexName, searchablePropertySet);

            }

            Class localType = type.getSuperclass();

            while ((localType != null) && !localType.equals(Object.class)) {

                Set<PropertyKey> superProperties = getSearchableProperties(localType, indexName);
                searchablePropertySet.addAll(superProperties);

                // include property sets from interfaces
                for (Class interfaceClass : getInterfacesForType(localType)) {
                    searchablePropertySet.addAll(getSearchableProperties(interfaceClass, indexName));
                }

                // one level up :)
                localType = localType.getSuperclass();

            }
        }
    }

    public static void scanEntity(Object entity) {

        Map<Field, PropertyKey> allProperties = getFieldValuesOfType(PropertyKey.class, entity);
        Map<Field, View> views = getFieldValuesOfType(View.class, entity);
        Class entityType = entity.getClass();

        for (Entry<Field, PropertyKey> entry : allProperties.entrySet()) {

            PropertyKey propertyKey = entry.getValue();
            Field field = entry.getKey();
            Class declaringClass = field.getDeclaringClass();

            if (declaringClass != null) {

                propertyKey.setDeclaringClass(declaringClass);
                registerProperty(declaringClass, propertyKey);

            }

            registerProperty(entityType, propertyKey);
        }

        for (Entry<Field, View> entry : views.entrySet()) {

            Field field = entry.getKey();
            View view = entry.getValue();

            for (PropertyKey propertyKey : view.properties()) {

                // register field in view for entity class and declaring superclass
                registerPropertySet(field.getDeclaringClass(), view.name(), propertyKey);
                registerPropertySet(entityType, view.name(), propertyKey);
            }
        }
    }

    public static void registerProperty(Class type, PropertyKey propertyKey) {

        getClassDBNamePropertyMapForType(type).put(propertyKey.dbName(), propertyKey);
        getClassJSNamePropertyMapForType(type).put(propertyKey.jsonName(), propertyKey);

        registerPropertySet(type, PropertyView.All, propertyKey);

        // inform property key of its registration
        propertyKey.registrationCallback(type);
    }

    /**
     * Initialize the entity context with all classes from the module service,
     */
    public static void init() {

        cachedEntities = Services.getService(ModuleService.class).getCachedNodeEntities();
    }

    /**
     * Register a listener to receive notifications about modified entities.
     * 
     * @param listener the listener to register
     */
    public static void registerTransactionListener(StructrTransactionListener listener) {
        transactionListeners.add(listener);
    }

    /**
     * Unregister a transaction listener.
     * 
     * @param listener the listener to unregister
     */
    public static void unregisterTransactionListener(StructrTransactionListener listener) {
        transactionListeners.remove(listener);
    }

    /**
     * Register a transformation that will be applied to every newly created entity of a given type.
     * 
     * @param type the type of the entities for which the transformation should be applied
     * @param transformation the transformation to apply on every entity
     */
    public static void registerEntityCreationTransformation(Class type,
            Transformation<GraphObject> transformation) {
        getEntityCreationTransformationsForType(type).add(transformation);
    }

    /**
     * Registers a property group for the given key of the given entity type. A property group can be
     * used to combine a set of properties into an object. {@see PropertyGroup}
     * 
     * @param type the type of the entities for which the property group should be registered
     * @param key the property key under which the property group should be visible
     * @param propertyGroup the property group
     */
    public static void registerPropertyGroup(Class type, PropertyKey key, PropertyGroup propertyGroup) {
        getPropertyGroupMapForType(type).put(key.dbName(), propertyGroup);
    }

    // ----- named relations -----
    /**
     * Registers a relationship entity with the given name and type to be instantiated for relationships
     * with type <code>relType</code> from <code>sourceType</code> to <code>destType</code>. Relationship
     * entity types registered with this method will be instantiated when a database relationship with
     * the given parameters are encountered.
     * 
     * @param relationName the name of the relationship as it should appear in the REST resource
     * @param relationshipEntityType the type of the relationship entity
     * @param sourceType the type of the source entity
     * @param destType the type of the destination entity
     * @param relType the relationship type
     */
    public static void registerNamedRelation(String relationName, Class relationshipEntityType, Class sourceType,
            Class destType, RelationshipType relType) {

        globalRelationshipNameMap.put(relationName,
                new RelationshipMapping(relationName, sourceType, destType, relType));
        globalRelationshipClassMap.put(createCombinedRelationshipType(sourceType.getSimpleName(), relType.name(),
                destType.getSimpleName()), relationshipEntityType);
    }

    // ----- property set methods -----
    /**
     * Registers the given set of property keys for the view with name <code>propertyView</code>
     * and the given prefix of entities with the given type.
     * 
     * @param type the type of the entities for which the view will be registered
     * @param propertyView the name of the property view for which the property set will be registered
     * @param viewPrefix a string that will be prepended to all property keys in this view
     * @param propertySet the set of property keys to register for the given view
     */
    public static void registerPropertySet(Class type, String propertyView, PropertyKey... propertySet) {

        Map<String, Set<PropertyKey>> propertyViewMap = getPropertyViewMapForType(type);
        Set<PropertyKey> properties = propertyViewMap.get(propertyView);

        if (properties == null) {
            properties = new LinkedHashSet<PropertyKey>();
            propertyViewMap.put(propertyView, properties);
        }

        // add all properties from set
        properties.addAll(Arrays.asList(propertySet));
    }

    // ----- validator methods -----
    /**
     * Registers a validator for the given property key of all entities of the given type.
     * 
     * @param type the type of the entities for which the validator should be registered
     * @param propertyKey the property key under which the validator should be registered
     * @param validatorClass the type of the validator to register
     */
    public static void registerPropertyValidator(Class type, PropertyKey propertyKey, PropertyValidator validator) {

        Map<PropertyKey, Set<PropertyValidator>> validatorMap = getPropertyValidatorMapForType(type);

        // fetch or create validator set
        Set<PropertyValidator> validators = validatorMap.get(propertyKey);

        if (validators == null) {

            validators = new LinkedHashSet<PropertyValidator>();

            validatorMap.put(propertyKey, validators);

        }

        validators.add(validator);

        // Automatically register the property key as searchable key,
        // so that the uniqueness validators work

        if (AbstractNode.class.isAssignableFrom(type)) {
            EntityContext.registerSearchableProperty(type, NodeIndex.keyword.name(), propertyKey);
        } else if (AbstractRelationship.class.isAssignableFrom(type)) {
            EntityContext.registerSearchableProperty(type, RelationshipIndex.rel_keyword.name(), propertyKey);
        }

    }

    // ----- searchable property map -----
    /**
     * Registers the given set of properties of the given entity type to be stored
     * in the given index.
     * 
     * @param type the entitiy type
     * @param index the index
     * @param keys the keys to be indexed
     */
    public static void registerSearchablePropertySet(Class type, String index, PropertyKey... keys) {

        for (PropertyKey key : keys) {

            registerSearchableProperty(type, index, key);

        }
    }

    /**
     * Registers the given property of the given entity type to be stored
     * in the given index.
     * 
     * @param type the entitiy type
     * @param index the index
     * @param key the key to be indexed
     */
    public static void registerSearchableProperty(Class type, String index, PropertyKey key) {

        Map<String, Set<PropertyKey>> searchablePropertyMapForType = getSearchablePropertyMapForType(type);
        Set<PropertyKey> searchablePropertySet = searchablePropertyMapForType.get(index);

        if (searchablePropertySet == null) {

            searchablePropertySet = new LinkedHashSet<PropertyKey>();

            searchablePropertyMapForType.put(index, searchablePropertySet);

        }

        key.registerSearchableProperties(searchablePropertySet);
    }

    // ----- private methods -----
    /**
     * Tries to normalize (and singularize) the given string so that it resolves to
     * an existing entity type.
     * 
     * @param possibleEntityString
     * @return the normalized entity name in its singular form
     */
    public static String normalizeEntityName(String possibleEntityString) {

        if (possibleEntityString == null) {

            return null;

        }

        if ("/".equals(possibleEntityString)) {

            return "/";

        }

        StringBuilder result = new StringBuilder();

        if (possibleEntityString.contains("/")) {

            String[] names = StringUtils.split(possibleEntityString, "/");

            for (String possibleEntityName : names) {

                // CAUTION: this cache might grow to a very large size, as it
                // contains all normalized mappings for every possible
                // property key / entity name that is ever called.
                String normalizedType = normalizedEntityNameCache.get(possibleEntityName);

                if (normalizedType == null) {

                    normalizedType = StringUtils.capitalize(CaseHelper.toUpperCamelCase(possibleEntityName));

                    if (normalizedType.endsWith("ies")) {

                        normalizedType = normalizedType.substring(0, normalizedType.length() - 3).concat("y");

                    } else if (!normalizedType.endsWith("ss") && normalizedType.endsWith("s")) {

                        logger.log(Level.FINEST, "Removing trailing plural 's' from type {0}", normalizedType);

                        normalizedType = normalizedType.substring(0, normalizedType.length() - 1);

                    }

                    // logger.log(Level.INFO, "String {0} normalized to {1}", new Object[] { possibleEntityName, normalizedType });
                    normalizedEntityNameCache.put(possibleEntityName, normalizedType);

                }

                result.append(normalizedType).append("/");

            }

            return StringUtils.removeEnd(result.toString(), "/");

        } else {

            //                      CAUTION: this cache might grow to a very large size, as it
            // contains all normalized mappings for every possible
            // property key / entity name that is ever called.
            String normalizedType = normalizedEntityNameCache.get(possibleEntityString);

            if (normalizedType == null) {

                normalizedType = StringUtils.capitalize(CaseHelper.toUpperCamelCase(possibleEntityString));

                if (normalizedType.endsWith("ies")) {

                    normalizedType = normalizedType.substring(0, normalizedType.length() - 3).concat("y");

                } else if (!normalizedType.endsWith("ss") && normalizedType.endsWith("s")) {

                    logger.log(Level.FINEST, "Removing trailing plural 's' from type {0}", normalizedType);

                    normalizedType = normalizedType.substring(0, normalizedType.length() - 1);

                }

                // logger.log(Level.INFO, "String {0} normalized to {1}", new Object[] { possibleEntityName, normalizedType });
                normalizedEntityNameCache.put(possibleEntityString, normalizedType);

            }

            return normalizedType;
        }
    }

    /**
     * Converts a Java class name (entity name) into a valid REST resource name.
     * 
     * @param normalizedEntityName
     * @return the given string in lowercase, with camel case occurences replaced by underscores
     */
    public static String denormalizeEntityName(String normalizedEntityName) {

        StringBuilder buf = new StringBuilder();

        for (char c : normalizedEntityName.toCharArray()) {

            if (Character.isUpperCase(c) && buf.length() > 0) {
                buf.append("_");
            }

            buf.append(Character.toLowerCase(c));
        }

        return buf.toString();
    }

    public static String createCombinedRelationshipType(String oldCombinedRelationshipType, Class newDestType) {

        String[] parts = getPartsOfCombinedRelationshipType(oldCombinedRelationshipType);

        return createCombinedRelationshipType(parts[0], parts[1], newDestType.getSimpleName());
    }

    public static String createCombinedRelationshipType(Class sourceType, RelationshipType relType,
            Class destType) {
        return createCombinedRelationshipType(sourceType.getSimpleName(), relType.name(), destType.getSimpleName());
    }

    public static String createCombinedRelationshipType(String sourceType, String relType, String destType) {

        StringBuilder buf = new StringBuilder();

        buf.append(sourceType);
        buf.append(COMBINED_RELATIONSHIP_KEY_SEP);
        buf.append(relType);
        buf.append(COMBINED_RELATIONSHIP_KEY_SEP);
        buf.append(destType);

        return buf.toString();
    }

    public static void registerConvertedProperty(PropertyKey propertyKey) {
        globalKnownPropertyKeys.add(propertyKey);
    }

    //~--- get methods ----------------------------------------------------

    public static Class getEntityClassForRawType(final String rawType) {

        String normalizedEntityName = normalizeEntityName(rawType);

        if (cachedEntities.containsKey(normalizedEntityName)) {

            return (Class) cachedEntities.get(normalizedEntityName);

        }

        if (reverseInterfaceMap.containsKey(normalizedEntityName)) {

            return (Class) reverseInterfaceMap.get(normalizedEntityName);

        }

        return null;
    }

    public static RelationshipMapping getNamedRelation(String relationName) {
        return globalRelationshipNameMap.get(relationName);
    }

    private static Class getSourceType(final String combinedRelationshipType) {

        String sourceType = getPartsOfCombinedRelationshipType(combinedRelationshipType)[0];
        Class realType = getEntityClassForRawType(sourceType);

        return realType;
    }

    private static RelationshipType getRelType(final String combinedRelationshipType) {
        String relType = getPartsOfCombinedRelationshipType(combinedRelationshipType)[1];
        return DynamicRelationshipType.withName(relType);
    }

    private static Class getDestType(final String combinedRelationshipType) {

        String destType = getPartsOfCombinedRelationshipType(combinedRelationshipType)[2];
        Class realType = getEntityClassForRawType(destType);

        return realType;
    }

    private static String[] getPartsOfCombinedRelationshipType(final String combinedRelationshipType) {
        return StringUtils.split(combinedRelationshipType, COMBINED_RELATIONSHIP_KEY_SEP);
    }

    public static Class getNamedRelationClass(String sourceType, String destType, String relType) {
        return getNamedRelationClass(createCombinedRelationshipType(sourceType, relType, destType));
    }

    public static Class getNamedRelationClass(String combinedRelationshipType) {

        Class sourceType = getSourceType(combinedRelationshipType);
        Class destType = getDestType(combinedRelationshipType);
        RelationshipType relType = getRelType(combinedRelationshipType);

        return getNamedRelationClass(sourceType, destType, relType);

    }

    public static Class getNamedRelationClass(Class sourceType, Class destType, RelationshipType relType) {

        Class namedRelationClass = null;
        Class sourceSuperClass = sourceType;
        Class destSuperClass = destType;

        while ((namedRelationClass == null) && !sourceSuperClass.equals(Object.class)
                && !destSuperClass.equals(Object.class)) {

            namedRelationClass = globalRelationshipClassMap
                    .get(createCombinedRelationshipType(sourceSuperClass, relType, destSuperClass));

            // check interfaces of dest class
            if (namedRelationClass == null) {

                for (Class interfaceClass : getInterfacesForType(destSuperClass)) {

                    namedRelationClass = globalRelationshipClassMap
                            .get(createCombinedRelationshipType(sourceSuperClass, relType, interfaceClass));
                    if (namedRelationClass != null) {
                        break;
                    }
                }
            }
            // do not check superclass for source type
            // sourceSuperClass = sourceSuperClass.getSuperclass();
            // one level up
            destSuperClass = destSuperClass.getSuperclass();

        }

        if (namedRelationClass != null) {

            return namedRelationClass;

        }

        return globalRelationshipClassMap.get(createCombinedRelationshipType(sourceType, relType, destType));
    }

    public static Collection<RelationshipMapping> getNamedRelations() {
        return globalRelationshipNameMap.values();
    }

    public static Set<StructrTransactionListener> getTransactionListeners() {
        return transactionListeners;
    }

    public static Set<Transformation<GraphObject>> getEntityCreationTransformations(Class type) {

        Set<Transformation<GraphObject>> transformations = new TreeSet<Transformation<GraphObject>>();
        Class localType = type;

        // collect for all superclasses
        while (localType != null && !localType.equals(Object.class)) {

            transformations.addAll(getEntityCreationTransformationsForType(localType));

            localType = localType.getSuperclass();

        }

        return transformations;
    }

    // ----- property notions -----
    public static PropertyGroup getPropertyGroup(Class type, PropertyKey key) {
        return getPropertyGroup(type, key.dbName());
    }

    public static PropertyGroup getPropertyGroup(Class type, String key) {

        PropertyGroup group = getAggregatedPropertyGroupMapForType(type).get(key);
        if (group == null) {

            Class localType = type;

            while (group == null && localType != null && !localType.equals(Object.class)) {

                group = getPropertyGroupMapForType(localType).get(key);

                if (group == null) {

                    // try interfaces as well
                    for (Class interfaceClass : getInterfacesForType(localType)) {

                        group = getPropertyGroupMapForType(interfaceClass).get(key);
                        if (group != null) {
                            break;
                        }
                    }
                }

                localType = localType.getSuperclass();
            }

            getAggregatedPropertyGroupMapForType(type).put(key, group);
        }

        return group;
    }

    // ----- view transformations -----
    public static void registerViewTransformation(Class type, String view, ViewTransformation transformation) {
        getViewTransformationMapForType(type).put(view, transformation);
    }

    public static ViewTransformation getViewTransformation(Class type, String view) {
        return getViewTransformationMapForType(type).get(view);
    }

    private static Map<String, ViewTransformation> getViewTransformationMapForType(Class type) {

        Map<String, ViewTransformation> viewTransformationMap = viewTransformations.get(type);
        if (viewTransformationMap == null) {
            viewTransformationMap = new LinkedHashMap<String, ViewTransformation>();
            viewTransformations.put(type, viewTransformationMap);
        }

        return viewTransformationMap;
    }

    // ----- property set methods -----
    public static Set<String> getPropertyViews() {

        Set<String> views = new LinkedHashSet<String>();

        // add all existing views
        for (Map<String, Set<PropertyKey>> view : globalPropertyViewMap.values()) {
            views.addAll(view.keySet());
        }

        return Collections.unmodifiableSet(views);
    }

    public static Set<PropertyKey> getPropertySet(Class type, String propertyView) {

        Map<String, Set<PropertyKey>> propertyViewMap = getPropertyViewMapForType(type);
        Set<PropertyKey> properties = propertyViewMap.get(propertyView);

        if (properties == null) {
            properties = new LinkedHashSet<PropertyKey>();
        }

        // read-only
        return Collections.unmodifiableSet(properties);
    }

    public static PropertyKey getPropertyKeyForDatabaseName(Class type, String dbName) {

        Map<String, PropertyKey> classDBNamePropertyMap = getClassDBNamePropertyMapForType(type);
        PropertyKey key = classDBNamePropertyMap.get(dbName);

        if (key == null) {

            // first try: uuid
            if (GraphObject.uuid.dbName().equals(dbName)) {
                return GraphObject.uuid;
            }

            //         logger.log(Level.WARNING, "Unable to determine property key for {0} of {1}, generic property key created!", new Object[] { dbName, type } );
            key = new GenericProperty(dbName);
        }

        return key;
    }

    public static PropertyKey getPropertyKeyForJSONName(Class type, String jsonName) {

        Map<String, PropertyKey> classJSNamePropertyMap = getClassJSNamePropertyMapForType(type);
        PropertyKey key = classJSNamePropertyMap.get(jsonName);

        if (key == null) {

            // first try: uuid
            if (GraphObject.uuid.dbName().equals(jsonName)) {
                return GraphObject.uuid;
            }

            //         logger.log(Level.WARNING, "Unable to determine property key for {0} of {1}, generic property key created!", new Object[] { jsonName, type } );
            key = new GenericProperty(jsonName);
        }

        return key;
    }

    public static Set<PropertyValidator> getPropertyValidators(final SecurityContext securityContext, Class type,
            PropertyKey propertyKey) {

        Set<PropertyValidator> validators = new LinkedHashSet<PropertyValidator>();
        Map<PropertyKey, Set<PropertyValidator>> validatorMap = null;
        Class localType = type;

        // try all superclasses
        while (localType != null && !localType.equals(Object.class)) {

            validatorMap = getPropertyValidatorMapForType(localType);

            Set<PropertyValidator> classValidators = validatorMap.get(propertyKey);
            if (classValidators != null) {
                validators.addAll(validatorMap.get(propertyKey));
            }

            // try converters from interfaces as well
            for (Class interfaceClass : getInterfacesForType(localType)) {
                Set<PropertyValidator> interfaceValidators = getPropertyValidatorMapForType(interfaceClass)
                        .get(propertyKey);
                if (interfaceValidators != null) {
                    validators.addAll(interfaceValidators);
                }
            }

            //                      logger.log(Level.INFO, "Validator class {0} found for type {1}", new Object[] { clazz != null ? clazz.getSimpleName() : "null", localType } );
            // one level up :)
            localType = localType.getSuperclass();

        }

        return validators;
    }

    public static Set<PropertyKey> getSearchableProperties(Class type, String index) {

        Set<PropertyKey> searchablePropertyMap = getSearchablePropertyMapForType(type).get(index);
        if (searchablePropertyMap == null) {

            searchablePropertyMap = new HashSet<PropertyKey>();
        }

        return searchablePropertyMap;
    }

    private static Map<String, Set<PropertyKey>> getPropertyViewMapForType(Class type) {

        Map<String, Set<PropertyKey>> propertyViewMap = globalPropertyViewMap.get(type);

        if (propertyViewMap == null) {

            propertyViewMap = new LinkedHashMap<String, Set<PropertyKey>>();

            globalPropertyViewMap.put(type, propertyViewMap);

        }

        return propertyViewMap;
    }

    private static Map<String, PropertyKey> getClassDBNamePropertyMapForType(Class type) {

        Map<String, PropertyKey> classDBNamePropertyMap = globalClassDBNamePropertyMap.get(type);

        if (classDBNamePropertyMap == null) {

            classDBNamePropertyMap = new LinkedHashMap<String, PropertyKey>();

            globalClassDBNamePropertyMap.put(type, classDBNamePropertyMap);

        }

        return classDBNamePropertyMap;
    }

    private static Map<String, PropertyKey> getClassJSNamePropertyMapForType(Class type) {

        Map<String, PropertyKey> classJSNamePropertyMap = globalClassJSNamePropertyMap.get(type);

        if (classJSNamePropertyMap == null) {

            classJSNamePropertyMap = new LinkedHashMap<String, PropertyKey>();

            globalClassJSNamePropertyMap.put(type, classJSNamePropertyMap);

        }

        return classJSNamePropertyMap;
    }

    private static Map<PropertyKey, Set<PropertyValidator>> getPropertyValidatorMapForType(Class type) {

        Map<PropertyKey, Set<PropertyValidator>> validatorMap = globalValidatorMap.get(type);

        if (validatorMap == null) {

            validatorMap = new LinkedHashMap<PropertyKey, Set<PropertyValidator>>();

            globalValidatorMap.put(type, validatorMap);

        }

        return validatorMap;
    }

    public static Map<String, Set<PropertyKey>> getSearchablePropertyMapForType(Class type) {

        Map<String, Set<PropertyKey>> searchablePropertyMap = globalSearchablePropertyMap.get(type);

        if (searchablePropertyMap == null) {

            searchablePropertyMap = new LinkedHashMap<String, Set<PropertyKey>>();

            globalSearchablePropertyMap.put(type, searchablePropertyMap);

        }

        return searchablePropertyMap;
    }

    private static Map<String, PropertyGroup> getAggregatedPropertyGroupMapForType(Class type) {

        Map<String, PropertyGroup> groupMap = globalAggregatedPropertyGroupMap.get(type);

        if (groupMap == null) {

            groupMap = new LinkedHashMap<String, PropertyGroup>();

            globalAggregatedPropertyGroupMap.put(type, groupMap);

        }

        return groupMap;
    }

    private static Map<String, PropertyGroup> getPropertyGroupMapForType(Class type) {

        Map<String, PropertyGroup> groupMap = globalPropertyGroupMap.get(type);

        if (groupMap == null) {

            groupMap = new LinkedHashMap<String, PropertyGroup>();

            globalPropertyGroupMap.put(type, groupMap);

        }

        return groupMap;
    }

    private static Set<Transformation<GraphObject>> getEntityCreationTransformationsForType(Class type) {

        Set<Transformation<GraphObject>> transformations = globalEntityCreationTransformationMap.get(type);

        if (transformations == null) {

            transformations = new LinkedHashSet<Transformation<GraphObject>>();

            globalEntityCreationTransformationMap.put(type, transformations);

        }

        return transformations;
    }

    public static Set<Class> getInterfacesForType(Class type) {

        Set<Class> interfaces = interfaceMap.get(type);
        if (interfaces == null) {

            interfaces = new LinkedHashSet<Class>();
            interfaceMap.put(type, interfaces);

            for (Class iface : type.getInterfaces()) {

                reverseInterfaceMap.put(iface.getSimpleName(), iface);
                interfaces.add(iface);
            }
        }

        return interfaces;
    }

    public static EntityContextModificationListener getTransactionEventHandler() {
        return globalModificationListener;
    }

    public static synchronized FrameworkException getFrameworkException(Long transactionKey) {
        return exceptionMap.get(transactionKey);
    }

    public static boolean isKnownProperty(final PropertyKey key) {
        return globalKnownPropertyKeys.contains(key);
    }

    public static boolean isSearchableProperty(Class type, String index, PropertyKey key) {

        boolean isSearchable = getSearchableProperties(type, index).contains(key);

        return isSearchable;
    }

    public static GenericFactory getGenericFactory() {
        return genericFactory;
    }

    public static void registerGenericFactory(GenericFactory factory) {
        genericFactory = factory;
    }

    public static synchronized TransactionChangeSet getTransactionChangeSet(long transactionKey) {
        return globalChangeSets.get(transactionKey);
    }

    public static synchronized void clearTransactionData(long transactionKey) {

        securityContextMap.remove();
        transactionKeyMap.remove();
        globalChangeSets.remove(transactionKey);
    }

    public static synchronized void setSecurityContext(SecurityContext securityContext) {
        securityContextMap.set(securityContext);
    }

    public static synchronized void setTransactionKey(Long transactionKey) {
        transactionKeyMap.set(transactionKey);
    }

    private static ArrayList<Node> sortNodes(final Iterable<Node> it) {

        ArrayList<Node> list = new ArrayList<Node>();

        for (Node p : it) {

            list.add(p);

        }

        Collections.sort(list, new Comparator<Node>() {

            @Override
            public int compare(Node o1, Node o2) {
                Long id1 = o1.getId();
                Long id2 = o2.getId();
                return id1.compareTo(id2);
            }
        });

        return list;

    }

    private static ArrayList<Relationship> sortRelationships(final Iterable<Relationship> it) {

        ArrayList<Relationship> list = new ArrayList<Relationship>();

        for (Relationship p : it) {

            list.add(p);

        }

        Collections.sort(list, new Comparator<Relationship>() {

            @Override
            public int compare(Relationship o1, Relationship o2) {
                Long id1 = o1.getId();
                Long id2 = o2.getId();
                return id1.compareTo(id2);
            }
        });

        return list;

    }

    private static <T> Map<Field, T> getFieldValuesOfType(Class<T> entityType, Object entity) {

        Map<Field, T> fields = new LinkedHashMap<Field, T>();
        Set<Class<?>> allTypes = getAllTypes(entity.getClass());

        for (Class<?> type : allTypes) {

            for (Field field : type.getDeclaredFields()) {

                if (entityType.isAssignableFrom(field.getType())) {

                    try {
                        fields.put(field, (T) field.get(entity));

                    } catch (Throwable t) {
                    }
                }
            }
        }

        return fields;
    }

    private static Set<Class<?>> getAllTypes(Class<?> type) {

        Set<Class<?>> types = new LinkedHashSet<Class<?>>();
        Class localType = type;

        do {

            collectAllInterfaces(localType, types);
            types.add(localType);

            localType = localType.getSuperclass();

        } while (!localType.equals(Object.class));

        return types;
    }

    private static void collectAllInterfaces(Class<?> type, Set<Class<?>> interfaces) {

        if (interfaces.contains(type)) {
            return;
        }

        for (Class iface : type.getInterfaces()) {

            collectAllInterfaces(iface, interfaces);
            interfaces.add(iface);
        }
    }

    //~--- inner classes --------------------------------------------------

    // <editor-fold defaultstate="collapsed" desc="EntityContextModificationListener">
    /**
     * Neo4j transaction event handler that implements the post-processing
     * of nodes and relationships in structr. This class handles the
     * change set of neo4j.
     */
    public static class EntityContextModificationListener implements TransactionEventHandler<Long> {

        // ----- interface TransactionEventHandler -----
        @Override
        public Long beforeCommit(TransactionData data) throws Exception {

            Long transactionKeyValue = transactionKeyMap.get();

            if (transactionKeyValue == null) {
                return -1L;
            }

            long transactionKey = transactionKeyMap.get();

            // check if node service is ready
            if (!Services.isReady(NodeService.class)) {

                logger.log(Level.WARNING, "Node service is not ready yet.");

                return transactionKey;

            }

            SecurityContext securityContext = securityContextMap.get();
            SecurityContext superUserContext = SecurityContext.getSuperUserInstance();
            NewIndexNodeCommand indexNodeCommand = Services.command(superUserContext, NewIndexNodeCommand.class);
            IndexRelationshipCommand indexRelationshipCommand = Services.command(superUserContext,
                    IndexRelationshipCommand.class);

            try {

                Map<Relationship, Map<String, Object>> removedRelProperties = new LinkedHashMap<Relationship, Map<String, Object>>();
                Map<Node, Map<String, Object>> removedNodeProperties = new LinkedHashMap<Node, Map<String, Object>>();
                RelationshipFactory relFactory = new RelationshipFactory(securityContext);
                TransactionChangeSet changeSet = new TransactionChangeSet();
                ErrorBuffer errorBuffer = new ErrorBuffer();
                NodeFactory nodeFactory = new NodeFactory(securityContext);
                boolean hasError = false;

                // store change set
                globalChangeSets.put(transactionKey, changeSet);

                // notify transaction listeners
                for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                    listener.begin(securityContext, transactionKey);
                }

                // collect properties
                hasError |= collectRemovedNodeProperties(securityContext, transactionKey, errorBuffer, data,
                        changeSet, nodeFactory, removedNodeProperties);
                hasError |= collectRemovedRelationshipProperties(securityContext, transactionKey, errorBuffer, data,
                        changeSet, relFactory, removedRelProperties);

                // call onCreation
                hasError |= callOnNodeCreation(securityContext, transactionKey, errorBuffer, data, changeSet,
                        nodeFactory);
                hasError |= callOnRelationshipCreation(securityContext, transactionKey, errorBuffer, data,
                        changeSet, relFactory, nodeFactory);

                // call onDeletion
                hasError |= callOnRelationshipDeletion(securityContext, transactionKey, errorBuffer, data,
                        changeSet, relFactory, nodeFactory, removedRelProperties);
                hasError |= callOnNodeDeletion(securityContext, transactionKey, errorBuffer, data, changeSet,
                        nodeFactory, removedNodeProperties);

                // call validators
                hasError |= callNodeValidators(securityContext, transactionKey, errorBuffer, data, changeSet,
                        nodeFactory);
                hasError |= callRelationshipValidators(securityContext, transactionKey, errorBuffer, data,
                        changeSet, relFactory);

                // call onModification
                hasError |= callOnNodeModification(securityContext, transactionKey, errorBuffer, changeSet,
                        indexNodeCommand);
                hasError |= callOnRelationshipModification(securityContext, transactionKey, errorBuffer, changeSet,
                        indexRelationshipCommand);

                // add created nodes to index
                for (AbstractNode node : changeSet.getCreatedNodes()) {
                    indexNodeCommand.addNode(node);
                }

                // add created relationships to index
                for (AbstractRelationship rel : changeSet.getCreatedRelationships()) {
                    indexRelationshipCommand.execute(rel);
                }

                if (hasError) {

                    for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                        listener.rollback(securityContext, transactionKey);
                    }

                    throw new FrameworkException(422, errorBuffer);

                }

                for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                    listener.commit(securityContext, transactionKey);
                }

            } catch (FrameworkException fex) {

                exceptionMap.put(transactionKey, fex);

                throw new IllegalStateException("Rollback");
            }

            return transactionKey;
        }

        @Override
        public void afterCommit(TransactionData data, Long transactionKey) {
        }

        @Override
        public void afterRollback(TransactionData data, Long transactionKey) {

            Throwable t = exceptionMap.get(transactionKey);

            if (t != null) {

                // thow
                throw new IllegalArgumentException(t);
            }
        }

        private boolean collectRemovedNodeProperties(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionData data, TransactionChangeSet changeSet,
                NodeFactory nodeFactory, Map<Node, Map<String, Object>> removedNodeProperties)
                throws FrameworkException {

            boolean hasError = false;

            for (PropertyEntry<Node> entry : data.removedNodeProperties()) {

                Node node = entry.entity();
                Map<String, Object> propertyMap = removedNodeProperties.get(node);

                if (propertyMap == null) {

                    propertyMap = new LinkedHashMap<String, Object>();

                    removedNodeProperties.put(node, propertyMap);

                }

                propertyMap.put(entry.key(), entry.previouslyCommitedValue());

                if (!data.isDeleted(node)) {

                    AbstractNode modifiedNode = nodeFactory.createNode(node, true, false);
                    if (modifiedNode != null) {

                        PropertyKey key = getPropertyKeyForDatabaseName(modifiedNode.getClass(), entry.key());

                        // only send modification events for non-system properties
                        if (!key.isSystemProperty()) {
                            changeSet.nonSystemProperty();
                        }

                        changeSet.modify(modifiedNode);

                        // notify registered listeners
                        for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                            hasError |= !listener.propertyRemoved(securityContext, transactionKey, errorBuffer,
                                    modifiedNode, key, entry.previouslyCommitedValue());
                        }
                    }

                }
            }

            return hasError;
        }

        private boolean collectRemovedRelationshipProperties(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionData data, TransactionChangeSet changeSet,
                RelationshipFactory relFactory, Map<Relationship, Map<String, Object>> removedRelProperties)
                throws FrameworkException {

            boolean hasError = false;

            for (PropertyEntry<Relationship> entry : data.removedRelationshipProperties()) {

                Relationship rel = entry.entity();
                Map<String, Object> propertyMap = removedRelProperties.get(rel);

                if (propertyMap == null) {

                    propertyMap = new LinkedHashMap<String, Object>();

                    removedRelProperties.put(rel, propertyMap);

                }

                propertyMap.put(entry.key(), entry.previouslyCommitedValue());

                if (!data.isDeleted(rel)) {

                    AbstractRelationship modifiedRel = relFactory.instantiateRelationship(securityContext, rel);
                    if (modifiedRel != null) {

                        PropertyKey key = getPropertyKeyForDatabaseName(modifiedRel.getClass(), entry.key());

                        // only send modification events for non-system properties
                        if (!key.isSystemProperty()) {
                            changeSet.nonSystemProperty();
                        }

                        changeSet.modify(modifiedRel);

                        // notify registered listeners
                        for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                            hasError |= !listener.propertyRemoved(securityContext, transactionKey, errorBuffer,
                                    modifiedRel, key, entry.previouslyCommitedValue());
                        }
                    }
                }

            }

            return hasError;
        }

        private boolean callOnNodeCreation(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionData data, TransactionChangeSet changeSet,
                NodeFactory nodeFactory) throws FrameworkException {

            boolean hasError = false;

            for (Node node : sortNodes(data.createdNodes())) {

                AbstractNode entity = nodeFactory.createNode(node, true, false);
                if (entity != null) {

                    hasError |= !entity.beforeCreation(securityContext, errorBuffer);

                    changeSet.create(entity);

                    // notify registered listeners
                    for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                        hasError |= !listener.graphObjectCreated(securityContext, transactionKey, errorBuffer,
                                entity);
                    }
                }

            }

            return hasError;
        }

        private boolean callOnRelationshipCreation(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionData data, TransactionChangeSet changeSet,
                RelationshipFactory relFactory, NodeFactory nodeFactory) throws FrameworkException {

            boolean hasError = false;

            for (Relationship rel : sortRelationships(data.createdRelationships())) {

                AbstractRelationship entity = relFactory.instantiateRelationship(securityContext, rel);
                if (entity != null) {

                    hasError |= !entity.beforeCreation(securityContext, errorBuffer);

                    changeSet.create(entity);

                    // notify registered listeners
                    for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                        hasError |= !listener.graphObjectCreated(securityContext, transactionKey, errorBuffer,
                                entity);
                    }

                    try {
                        AbstractNode startNode = nodeFactory.createNode(rel.getStartNode());
                        RelationshipType relationshipType = entity.getRelType();

                        if (startNode != null) {

                            changeSet.modifyRelationshipEndpoint(startNode, relationshipType);
                        }

                        AbstractNode endNode = nodeFactory.createNode(rel.getEndNode());
                        if (endNode != null) {

                            changeSet.modifyRelationshipEndpoint(endNode, relationshipType);
                        }

                    } catch (Throwable t) {
                    }
                }

            }

            return hasError;
        }

        private boolean callOnRelationshipDeletion(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionData data, TransactionChangeSet changeSet,
                RelationshipFactory relFactory, NodeFactory nodeFactory,
                Map<Relationship, Map<String, Object>> removedRelProperties) throws FrameworkException {

            boolean hasError = false;

            for (Relationship rel : data.deletedRelationships()) {

                AbstractRelationship entity = relFactory.instantiateRelationship(securityContext, rel);
                if (entity != null) {

                    // convertFromInput properties
                    PropertyMap properties = PropertyMap.databaseTypeToJavaType(securityContext, entity,
                            removedRelProperties.get(rel));

                    hasError |= !entity.beforeDeletion(securityContext, errorBuffer, properties);

                    // notify registered listeners
                    for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                        hasError |= !listener.graphObjectDeleted(securityContext, transactionKey, errorBuffer,
                                entity, properties);
                    }

                    changeSet.delete(entity);

                    try {
                        AbstractNode startNode = nodeFactory.createNode(rel.getStartNode());
                        RelationshipType relationshipType = entity.getRelType();

                        if (startNode != null) {

                            changeSet.modifyRelationshipEndpoint(startNode, relationshipType);
                        }

                        AbstractNode endNode = nodeFactory.createNode(rel.getEndNode());
                        if (endNode != null) {

                            changeSet.modifyRelationshipEndpoint(endNode, relationshipType);
                        }

                    } catch (Throwable ignore) {
                    }
                }

            }

            return hasError;
        }

        private boolean callOnNodeDeletion(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionData data, TransactionChangeSet changeSet,
                NodeFactory nodeFactory, Map<Node, Map<String, Object>> removedNodeProperties)
                throws FrameworkException {

            boolean hasError = false;

            for (Node node : data.deletedNodes()) {

                String type = (String) removedNodeProperties.get(node).get(AbstractNode.type.dbName());
                AbstractNode entity = nodeFactory.createDummyNode(type);

                if (entity != null) {

                    PropertyMap properties = PropertyMap.databaseTypeToJavaType(securityContext, entity,
                            removedNodeProperties.get(node));

                    hasError |= !entity.beforeDeletion(securityContext, errorBuffer, properties);

                    // notify registered listeners
                    for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                        hasError |= !listener.graphObjectDeleted(securityContext, transactionKey, errorBuffer,
                                entity, properties);
                    }

                    changeSet.delete(entity);
                }
            }

            return hasError;
        }

        private boolean callNodeValidators(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionData data, TransactionChangeSet changeSet,
                NodeFactory nodeFactory) throws FrameworkException {

            boolean hasError = false;

            for (PropertyEntry<Node> entry : data.assignedNodeProperties()) {

                AbstractNode nodeEntity = nodeFactory.createNode(entry.entity(), true, false);
                if (nodeEntity != null) {

                    PropertyKey key = getPropertyKeyForDatabaseName(nodeEntity.getClass(), entry.key());
                    Object value = entry.value();

                    // only send modification events for non-system properties
                    if (!key.isSystemProperty()) {

                        changeSet.nonSystemProperty();
                        changeSet.modify(nodeEntity);
                    }

                    // iterate over validators
                    // FIXME: synthetic property key
                    Set<PropertyValidator> validators = EntityContext.getPropertyValidators(securityContext,
                            nodeEntity.getClass(), key);

                    if (validators != null) {

                        for (PropertyValidator validator : validators) {

                            hasError |= !(validator.isValid(nodeEntity, key, value, errorBuffer));

                        }

                    }

                    // notify registered listeners
                    for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                        hasError |= !listener.propertyModified(securityContext, transactionKey, errorBuffer,
                                nodeEntity, key, entry.previouslyCommitedValue(), value);
                    }
                }
            }

            return hasError;

        }

        private boolean callRelationshipValidators(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionData data, TransactionChangeSet changeSet,
                RelationshipFactory relFactory) throws FrameworkException {

            boolean hasError = false;

            for (PropertyEntry<Relationship> entry : data.assignedRelationshipProperties()) {

                AbstractRelationship relEntity = relFactory.instantiateRelationship(securityContext,
                        entry.entity());
                if (relEntity != null) {

                    PropertyKey key = getPropertyKeyForDatabaseName(relEntity.getClass(), entry.key());
                    Object value = entry.value();

                    // only send modification events for non-system properties
                    if (!key.isSystemProperty()) {
                        changeSet.nonSystemProperty();
                        changeSet.modify(relEntity);
                    }

                    // iterate over validators
                    // FIXME: synthetic property key
                    Set<PropertyValidator> validators = EntityContext.getPropertyValidators(securityContext,
                            relEntity.getClass(), key);

                    if (validators != null) {

                        for (PropertyValidator validator : validators) {

                            hasError |= !(validator.isValid(relEntity, key, value, errorBuffer));

                        }

                    }

                    // notify registered listeners
                    for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                        hasError |= !listener.propertyModified(securityContext, transactionKey, errorBuffer,
                                relEntity, key, entry.previouslyCommitedValue(), value);
                    }
                }
            }

            return hasError;
        }

        private boolean callOnNodeModification(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionChangeSet changeSet, NewIndexNodeCommand indexNodeCommand)
                throws FrameworkException {

            boolean hasError = false;

            for (AbstractNode node : changeSet.getModifiedNodes()) {

                hasError |= !node.beforeModification(securityContext, errorBuffer);

                // notify registered listeners
                for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                    hasError |= !listener.graphObjectModified(securityContext, transactionKey, errorBuffer, node);
                }

                indexNodeCommand.updateNode(node);
            }

            return hasError;
        }

        private boolean callOnRelationshipModification(SecurityContext securityContext, long transactionKey,
                ErrorBuffer errorBuffer, TransactionChangeSet changeSet,
                IndexRelationshipCommand indexRelationshipCommand) throws FrameworkException {

            boolean hasError = false;

            for (AbstractRelationship rel : changeSet.getModifiedRelationships()) {

                hasError |= !rel.beforeModification(securityContext, errorBuffer);

                // notify registered listeners
                for (StructrTransactionListener listener : EntityContext.getTransactionListeners()) {
                    hasError |= !listener.graphObjectModified(securityContext, transactionKey, errorBuffer, rel);
                }

                indexRelationshipCommand.execute(rel);
            }

            return hasError;
        }
    }
    // </editor-fold>
}