EntityContext

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<String>> globalWriteOncePropertyMap = new LinkedHashMap<Class, Set<String>>();
    private static final Map<Class, Map<String, Set<PropertyValidator>>> globalValidatorMap = new LinkedHashMap<Class, Map<String, Set<PropertyValidator>>>();
    private static final Map<String, Map<String, Set<String>>> globalSearchablePropertyMap = new LinkedHashMap<String, Map<String, Set<String>>>();

    // This map contains a mapping from (sourceType, destType) -> RelationClass
    private static final Map<Class, Map<Class, RelationClass>> globalRelationClassMap = new LinkedHashMap<Class, Map<Class, RelationClass>>();
    private static final Map<Class, Set<String>> globalReadOnlyPropertyMap = new LinkedHashMap<Class, Set<String>>();
    private static final Map<Class, Map<String, Set<String>>> globalPropertyViewMap = new LinkedHashMap<Class, Map<String, Set<String>>>();

    // This map contains a mapping from (sourceType, propertyKey) -> RelationClass
    private static final Map<Class, Map<String, RelationClass>> globalPropertyRelationClassMap = new LinkedHashMap<Class, Map<String, RelationClass>>();
    private static final Map<Class, Map<String, PropertyGroup>> globalPropertyGroupMap = new LinkedHashMap<Class, Map<String, PropertyGroup>>();
    private static final Map<Class, Map<String, Class<? extends PropertyConverter>>> globalPropertyConverterMap = new LinkedHashMap<Class, Map<String, Class<? extends PropertyConverter>>>();

    // This set contains all known properties
    private static final Set<String> globalKnownPropertyKeys = new LinkedHashSet<String>();
    private static final Map<Class, Set<Transformation<GraphObject>>> globalEntityCreationTransformationMap = new LinkedHashMap<Class, Set<Transformation<GraphObject>>>();
    private static final Map<Class, Map<String, Object>> globalDefaultValueMap = new LinkedHashMap<Class, Map<String, Object>>();
    private static final Map<Class, Map<String, Value>> globalConversionParameterMap = new LinkedHashMap<Class, Map<String, Value>>();
    private static final Map<String, String> normalizedEntityNameCache = new LinkedHashMap<String, String>();
    private static final Set<VetoableGraphObjectListener> modificationListeners = new LinkedHashSet<VetoableGraphObjectListener>();
    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<Thread, SecurityContext> securityContextMap = Collections.synchronizedMap(new WeakHashMap<Thread, SecurityContext>());
    private static final Map<Thread, Long> transactionKeyMap = Collections.synchronizedMap(new WeakHashMap<Thread, Long>());
    private static Map<String, Class> cachedEntities = new LinkedHashMap<String, Class>();

    //~--- 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<String>> searchablePropertyMapForType = getSearchablePropertyMapForType(type.getSimpleName());
            Set<String> searchablePropertySet = searchablePropertyMapForType.get(indexName);

            if (searchablePropertySet == null)
            {

                searchablePropertySet = new LinkedHashSet<String>();

                searchablePropertyMapForType.put(indexName, searchablePropertySet);

            }

            Class superClass = type.getSuperclass();

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

                Set<String> superProperties = getSearchableProperties(superClass, indexName);

                searchablePropertySet.addAll(superProperties);

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

            }

        }
    }

    public static void init()
    {

        try
        {
            cachedEntities = (Map<String, Class>) Services.command(SecurityContext.getSuperUserInstance(), GetEntitiesCommand.class).execute();
        }
        catch (FrameworkException ex)
        {
            Logger.getLogger(EntityContext.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void registerModificationListener(VetoableGraphObjectListener listener)
    {
        modificationListeners.add(listener);
    }

    public static void unregisterModificationListener(VetoableGraphObjectListener listener)
    {
        modificationListeners.remove(listener);
    }

    public static void registerEntityCreationTransformation(Class type, Transformation<GraphObject> transformation)
    {
        getEntityCreationTransformationsForType(type).add(transformation);
    }

    public static void registerPropertyGroup(Class type, PropertyKey key, PropertyGroup propertyGroup)
    {
        getPropertyGroupMapForType(type).put(key.name(), propertyGroup);
    }

    // ----- default values -----
    public static void registerDefaultValue(Class type, PropertyKey propertyKey, Object defaultValue)
    {
        getGlobalDefaultValueMapForType(type).put(propertyKey.name(), defaultValue);
    }

    // ----- named relations -----
    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 and entity relationships -----
    public static void registerPropertyRelation(Class sourceType, PropertyKey propertyKey, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality)
    {
        registerPropertyRelation(sourceType, propertyKey, destType, relType, direction, cardinality, RelationClass.DELETE_NONE);
    }

    public static void registerPropertyRelation(Class sourceType, PropertyKey propertyKey, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality,
                                                int cascadeDelete)
    {
        registerPropertyRelation(sourceType, propertyKey.name(), destType, relType, direction, cardinality, cascadeDelete);
    }

    public static void registerPropertyRelation(Class sourceType, String property, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, int cascadeDelete)
    {

        // need to set type here
        Notion objectNotion = new ObjectNotion();

        objectNotion.setType(destType);
        registerPropertyRelationInternal(sourceType, property, destType, relType, direction, cardinality, objectNotion, cascadeDelete);
    }

    public static void registerPropertyRelation(Class sourceType, PropertyKey[] propertySet, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality,
                                                Notion notion)
    {
        registerPropertyRelation(sourceType, propertySet, destType, relType, direction, cardinality, notion, RelationClass.DELETE_NONE);
    }

    public static void registerPropertyRelation(Class sourceType, PropertyKey[] propertySet, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, Notion notion,
                                                int cascadeDelete)
    {

        notion.setType(destType);
        registerPropertyRelationInternal(sourceType, propertySet, destType, relType, direction, cardinality, notion, cascadeDelete);
    }

    public static void registerPropertyRelation(Class sourceType, PropertyKey propertyKey, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, Notion notion)
    {
        registerPropertyRelation(sourceType, propertyKey, destType, relType, direction, cardinality, notion, RelationClass.DELETE_NONE);
    }

    public static void registerPropertyRelation(Class sourceType, PropertyKey propertyKey, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, Notion notion,
                                                int cascadeDelete)
    {
        registerPropertyRelation(sourceType, propertyKey.name(), destType, relType, direction, cardinality, notion, cascadeDelete);
    }

    public static void registerPropertyRelation(Class sourceType, String property, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, Notion notion,
                                                int cascadeDelete)
    {

        notion.setType(destType);
        registerPropertyRelationInternal(sourceType, property, destType, relType, direction, cardinality, notion, cascadeDelete);
    }

    public static void registerEntityRelation(Class sourceType, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality)
    {
        registerEntityRelation(sourceType, destType, relType, direction, cardinality, RelationClass.DELETE_NONE);
    }

    public static void registerEntityRelation(Class sourceType, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, int cascadeDelete)
    {

        // need to set type here
        Notion objectNotion = new ObjectNotion();

        objectNotion.setType(destType);
        registerEntityRelationInternal(sourceType, destType, relType, direction, cardinality, objectNotion, cascadeDelete);
    }

    public static void registerEntityRelation(Class sourceType, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, Notion notion)
    {
        registerEntityRelation(sourceType, destType, relType, direction, cardinality, notion, RelationClass.DELETE_NONE);
    }

    public static void registerEntityRelation(Class sourceType, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, Notion notion, int cascadeDelete)
    {

        notion.setType(destType);
        registerEntityRelationInternal(sourceType, destType, relType, direction, cardinality, notion, cascadeDelete);
    }

    // ----- property set methods -----
    public static void registerPropertySet(Class type, String propertyView, PropertyKey... propertySet)
    {

        Set<String> properties = getPropertySet(type, propertyView);

        for (PropertyKey property : propertySet)
        {

            properties.add(property.name());

        }

        // include property sets from superclass
        Class superClass = type.getSuperclass();

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

            Set<String> superProperties = getPropertySet(superClass, propertyView);

            properties.addAll(superProperties);

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

        }
    }

    public static void clearPropertySet(Class type, String propertyView)
    {
        getPropertySet(type, propertyView).clear();
    }

    // ----- validator methods -----
    public static void registerPropertyValidator(Class type, PropertyKey propertyKey, PropertyValidator validatorClass)
    {
        registerPropertyValidator(type, propertyKey.name(), validatorClass);
    }

    public static void registerPropertyValidator(Class type, String propertyKey, PropertyValidator validator)
    {

        Map<String, 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);
    }

    // ----- PropertyConverter methods -----
    public static void registerPropertyConverter(Class type, PropertyKey propertyKey, Class<? extends PropertyConverter> propertyConverterClass)
    {
        registerPropertyConverter(type, propertyKey.name(), propertyConverterClass);
    }

    public static void registerPropertyConverter(Class type, PropertyKey propertyKey, Class<? extends PropertyConverter> propertyConverterClass, Value value)
    {
        registerPropertyConverter(type, propertyKey.name(), propertyConverterClass, value);
    }

    public static void registerPropertyConverter(Class type, String propertyKey, Class<? extends PropertyConverter> propertyConverterClass)
    {
        registerPropertyConverter(type, propertyKey, propertyConverterClass, null);
    }

    public static void registerPropertyConverter(Class type, String propertyKey, Class<? extends PropertyConverter> propertyConverterClass, Value value)
    {

        getPropertyConverterMapForType(type).put(propertyKey, propertyConverterClass);

        if (value != null)
        {

            getPropertyConversionParameterMapForType(type).put(propertyKey, value);
            globalKnownPropertyKeys.add(propertyKey);

        }
    }

    // ----- read-only property map -----
    public static void registerReadOnlyProperty(Class type, String key)
    {
        getReadOnlyPropertySetForType(type).add(key);
    }

    // ----- searchable property map -----
    public static void registerSearchablePropertySet(Class type, String index, PropertyKey... keys)
    {

        for (PropertyKey key : keys)
        {

            registerSearchableProperty(type, index, key);

        }
    }

    public static void registerSearchableProperty(Class type, String index, PropertyKey key)
    {
        registerSearchableProperty(type, index, key.name());
    }

    //
//      public static void registerSearchableProperty(String type, String index, String key) {
//              registerSearchableProperty(type.getSimpleName(), index, key);
//      }
    public static void registerSearchableProperty(Class type, String index, String key)
    {

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

        if (searchablePropertySet == null)
        {

            searchablePropertySet = new LinkedHashSet<String>();

            searchablePropertyMapForType.put(index, searchablePropertySet);

        }

        searchablePropertySet.add(key);
    }

    // ----- write-once property map -----
    public static void registerWriteOnceProperty(Class type, String key)
    {
        getWriteOncePropertySetForType(type).add(key);
    }

    // ----- private methods -----
    public static String normalizeEntityName(String possibleEntityName)
    {

        // 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("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);

        }

        return normalizedType;
    }

    private static void registerPropertyRelationInternal(Class sourceType, PropertyKey[] properties, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality,
                                                         Notion notion, int cascadeDelete)
    {

        Map<String, RelationClass> typeMap = getPropertyRelationshipMapForType(sourceType);
        RelationClass rel = new RelationClass(destType, relType, direction, cardinality, notion, cascadeDelete);

        for (PropertyKey prop : properties)
        {

            typeMap.put(prop.name(), rel);
            globalKnownPropertyKeys.add(prop.name());

        }
    }

    private static void registerPropertyRelationInternal(Class sourceType, String property, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, Notion notion,
                                                         int cascadeDelete)
    {

        Map<String, RelationClass> typeMap = getPropertyRelationshipMapForType(sourceType);

        typeMap.put(property, new RelationClass(destType, relType, direction, cardinality, notion, cascadeDelete));
        globalKnownPropertyKeys.add(property);
    }

    private static void registerEntityRelationInternal(Class sourceType, Class destType, RelationshipType relType, Direction direction, Cardinality cardinality, Notion notion, int cascadeDelete)
    {

        Map<Class, RelationClass> typeMap = getRelationClassMapForType(sourceType);
        RelationClass directedRelation = new RelationClass(destType, relType, direction, cardinality, notion, cascadeDelete);

        typeMap.put(destType, directedRelation);
    }

    public static synchronized void removeTransactionKey()
    {
        transactionKeyMap.remove(Thread.currentThread());
    }

    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 synchronized void removeSecurityContext()
    {
        securityContextMap.remove(Thread.currentThread());
    }

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

    public static Class getEntityClassForRawType(final String rawType)
    {

        String normalizedEntityName = normalizeEntityName(rawType);

        if (cachedEntities.containsKey(normalizedEntityName))
        {

            return (Class) cachedEntities.get(normalizedEntityName);

        }

        return null;
    }

    public static Object getDefaultValue(Class type, String propertyKey)
    {
        return getGlobalDefaultValueMapForType(type).get(propertyKey);
    }

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

    private static Class getDestType(final String combinedRelationshipType)
    {

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

        try
        {
            realType = (Class) Services.command(null, GetEntityClassCommand.class).execute(StringUtils.capitalize(destType));
        }
        catch (FrameworkException ex)
        {
            logger.log(Level.WARNING, "No real type found for {0}", 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 namedRelationClass = globalRelationshipClassMap.get(combinedRelationshipType);
        Class destType = getDestType(combinedRelationshipType);

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

            combinedRelationshipType = createCombinedRelationshipType(combinedRelationshipType, destType);
            namedRelationClass = globalRelationshipClassMap.get(combinedRelationshipType);
            destType = destType.getSuperclass();

        }

        return namedRelationClass;
    }

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

    public static Set<VetoableGraphObjectListener> getModificationListeners()
    {
        return modificationListeners;
    }

    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.equals(Object.class))
        {

            transformations.addAll(getEntityCreationTransformationsForType(localType));

            localType = localType.getSuperclass();

        }

//              return new TreeSet<Transformation<AbstractNode>>(transformations).descendingSet();
        return transformations;
    }

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

    public static PropertyGroup getPropertyGroup(Class type, String key)
    {
        return getPropertyGroupMapForType(type).get(key);
    }

    // ----- static relationship methods -----
    public static RelationClass getRelationClass(Class sourceType, Class destType)
    {

        RelationClass relation = null;
        Class localType = sourceType;

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

            relation = getRelationClassMapForType(localType).get(destType);
            localType = localType.getSuperclass();

        }

        return relation;
    }

    public static RelationClass getRelationClass(Class sourceType, String key)
    {

        Class destType = getEntityClassForRawType(key);

        if (destType != null)
        {

            return getRelationClass(sourceType, destType);

        }

        return getRelationClassForProperty(sourceType, key);
    }

    public static RelationClass getRelationClassForProperty(Class sourceType, String propertyKey)
    {

        RelationClass relation = null;
        Class localType = sourceType;

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

            relation = getPropertyRelationshipMapForType(localType).get(propertyKey);
            localType = localType.getSuperclass();

        }

        return relation;
    }

    // ----- property set methods -----
    public static Set<String> getPropertySet(Class type, String propertyView)
    {

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

        if (propertySet == null)
        {

            propertySet = new LinkedHashSet<String>();

            propertyViewMap.put(propertyView, propertySet);

            // test: fill property set with values from supertypes
            Class superClass = type.getSuperclass();

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

                Set<String> superProperties = getPropertySet(superClass, propertyView);

                propertySet.addAll(superProperties);

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

            }

        }

        return propertySet;
    }

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

        Map<String, Set<PropertyValidator>> validatorMap = null;
        Set<PropertyValidator> validators = null;
        Class localType = type;

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

            validatorMap = getPropertyValidatorMapForType(localType);
            validators = validatorMap.get(propertyKey);

//                      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 PropertyConverter getPropertyConverter(final SecurityContext securityContext, Class type, String propertyKey)
    {

        Map<String, Class<? extends PropertyConverter>> converterMap = null;
        PropertyConverter propertyConverter = null;
        Class localType = type;
        Class clazz = null;

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

            converterMap = getPropertyConverterMapForType(localType);
            clazz = converterMap.get(propertyKey);

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

        }

        if (clazz != null)
        {

            try
            {

                propertyConverter = (PropertyConverter) clazz.newInstance();

                propertyConverter.setSecurityContext(securityContext);

            }
            catch (Throwable t)
            {
                logger.log(Level.WARNING, "Unable to instantiate property PropertyConverter {0}: {1}", new Object[]{clazz.getName(), t.getMessage()});
            }

        }

        return propertyConverter;
    }

    public static Value getPropertyConversionParameter(Class type, String propertyKey)
    {

        Map<String, Value> conversionParameterMap = null;
        Class localType = type;
        Value value = null;

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

            conversionParameterMap = getPropertyConversionParameterMapForType(localType);
            value = conversionParameterMap.get(propertyKey);

//                      logger.log(Level.INFO, "Conversion parameter value {0} found for type {1}", new Object[] { value != null ? value.getClass().getSimpleName() : "null", localType } );
            localType = localType.getSuperclass();

        }

        return value;
    }

    public static Set<String> getSearchableProperties(Class type, String index)
    {
        return getSearchableProperties(type.getSimpleName(), index);
    }

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

        Set<String> searchablePropertyMap = getSearchablePropertyMapForType(type).get(index);

        if (searchablePropertyMap == null)
        {

            searchablePropertyMap = new HashSet<String>();

        }

        return searchablePropertyMap;
    }

    private static Map<String, RelationClass> getPropertyRelationshipMapForType(Class sourceType)
    {

        Map<String, RelationClass> typeMap = globalPropertyRelationClassMap.get(sourceType);

        if (typeMap == null)
        {

            typeMap = new LinkedHashMap<String, RelationClass>();

            globalPropertyRelationClassMap.put(sourceType, typeMap);

        }

        return (typeMap);
    }

    private static Map<Class, RelationClass> getRelationClassMapForType(Class sourceType)
    {

        Map<Class, RelationClass> typeMap = globalRelationClassMap.get(sourceType);

        if (typeMap == null)
        {

            typeMap = new LinkedHashMap<Class, RelationClass>();

            globalRelationClassMap.put(sourceType, typeMap);

        }

        return (typeMap);
    }

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

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

        if (propertyViewMap == null)
        {

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

            globalPropertyViewMap.put(type, propertyViewMap);

        }

        return propertyViewMap;
    }

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

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

        if (validatorMap == null)
        {

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

            globalValidatorMap.put(type, validatorMap);

        }

        return validatorMap;
    }

    private static Map<String, Class<? extends PropertyConverter>> getPropertyConverterMapForType(Class type)
    {

        Map<String, Class<? extends PropertyConverter>> PropertyConverterMap = globalPropertyConverterMap.get(type);

        if (PropertyConverterMap == null)
        {

            PropertyConverterMap = new LinkedHashMap<String, Class<? extends PropertyConverter>>();

            globalPropertyConverterMap.put(type, PropertyConverterMap);

        }

        return PropertyConverterMap;
    }

    private static Map<String, Object> getGlobalDefaultValueMapForType(Class type)
    {

        Map<String, Object> defaultValueMap = globalDefaultValueMap.get(type);

        if (defaultValueMap == null)
        {

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

            globalDefaultValueMap.put(type, defaultValueMap);

        }

        return defaultValueMap;
    }

    private static Map<String, Value> getPropertyConversionParameterMapForType(Class type)
    {

        Map<String, Value> conversionParameterMap = globalConversionParameterMap.get(type);

        if (conversionParameterMap == null)
        {

            conversionParameterMap = new LinkedHashMap<String, Value>();

            globalConversionParameterMap.put(type, conversionParameterMap);

        }

        return conversionParameterMap;
    }

    private static Set<String> getReadOnlyPropertySetForType(Class type)
    {

        Set<String> readOnlyPropertySet = globalReadOnlyPropertyMap.get(type);

        if (readOnlyPropertySet == null)
        {

            readOnlyPropertySet = new LinkedHashSet<String>();

            globalReadOnlyPropertyMap.put(type, readOnlyPropertySet);

        }

        return readOnlyPropertySet;
    }

    private static Set<String> getWriteOncePropertySetForType(Class type)
    {

        Set<String> writeOncePropertySet = globalWriteOncePropertyMap.get(type);

        if (writeOncePropertySet == null)
        {

            writeOncePropertySet = new LinkedHashSet<String>();

            globalWriteOncePropertyMap.put(type, writeOncePropertySet);

        }

        return writeOncePropertySet;
    }

    private static Map<String, Set<String>> getSearchablePropertyMapForType(String sourceType)
    {

        Map<String, Set<String>> searchablePropertyMap = globalSearchablePropertyMap.get(normalizeEntityName(sourceType));

        if (searchablePropertyMap == null)
        {

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

            globalSearchablePropertyMap.put(normalizeEntityName(sourceType), searchablePropertyMap);

        }

        return searchablePropertyMap;
    }

    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 EntityContextModificationListener getTransactionEventHandler()
    {
        return globalModificationListener;
    }

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

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

    public static boolean isReadOnlyProperty(Class type, String key)
    {

        boolean isReadOnly = getReadOnlyPropertySetForType(type).contains(key);
        Class localType = type;

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

            isReadOnly = getReadOnlyPropertySetForType(localType).contains(key);

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

        }

        return isReadOnly;
    }

    public static boolean isSearchableProperty(Class type, String index, PropertyKey key)
    {
        return isSearchableProperty(type, index, key.name());
    }

    public static boolean isSearchableProperty(Class type, String index, String key)
    {
        return isSearchableProperty(type.getSimpleName(), index, key);
    }

    public static boolean isSearchableProperty(String type, String index, String key)
    {
        return getSearchablePropertyMapForType(normalizeEntityName(type)).containsKey(key);
    }

    public static boolean isWriteOnceProperty(Class type, String key)
    {

        boolean isWriteOnce = getWriteOncePropertySetForType(type).contains(key);
        Class localType = type;

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

            isWriteOnce = getWriteOncePropertySetForType(localType).contains(key);

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

        }

        return isWriteOnce;
    }

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

    public static synchronized void setSecurityContext(SecurityContext securityContext)
    {
        securityContextMap.put(Thread.currentThread(), securityContext);
    }

    public static synchronized void setTransactionKey(Long transactionKey)
    {
        transactionKeyMap.put(Thread.currentThread(), transactionKey);
    }

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

    // <editor-fold defaultstate="collapsed" desc="EntityContextModificationListener">
    public static class EntityContextModificationListener implements VetoableGraphObjectListener, TransactionEventHandler<Long>
    {

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

            SecurityContext securityContext = securityContextMap.get(Thread.currentThread());
            Command indexNodeCommand = Services.command(securityContext, IndexNodeCommand.class);
            Command indexRelationshipCommand = Services.command(securityContext, IndexRelationshipCommand.class);
            long transactionKey = transactionKeyMap.get(Thread.currentThread());

            try
            {

                ErrorBuffer errorBuffer = new ErrorBuffer();
                boolean hasError = false;
                Map<Node, Map<String, Object>> removedNodeProperties = new LinkedHashMap<Node, Map<String, Object>>();
                Map<Relationship, Map<String, Object>> removedRelProperties = new LinkedHashMap<Relationship, Map<String, Object>>();
                NodeFactory nodeFactory = new NodeFactory(securityContext);
                RelationshipFactory relFactory = new RelationshipFactory(securityContext);
                Set<AbstractNode> modifiedNodes = new LinkedHashSet<AbstractNode>();
                Set<AbstractNode> createdNodes = new LinkedHashSet<AbstractNode>();
                Set<AbstractRelationship> modifiedRels = new LinkedHashSet<AbstractRelationship>();
                Set<AbstractRelationship> createdRels = new LinkedHashSet<AbstractRelationship>();
                Set<AbstractRelationship> deletedRels = new LinkedHashSet<AbstractRelationship>();

                // 0: notify listeners of beginning commit
                begin(securityContext, transactionKey, errorBuffer);

                // 1.1: collect properties of deleted nodes
                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());

                }

                // 1.2: collect properties of deleted relationships
                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());

                }

                // 2: notify listeners of node creation (so the modifications can later be tracked)
                for (Node node : data.createdNodes())
                {

                    AbstractNode entity = nodeFactory.createNode(securityContext, node);

                    hasError |= graphObjectCreated(securityContext, transactionKey, errorBuffer, entity);

                    createdNodes.add(entity);

                }

                // 3: notify listeners of relationship creation
                for (Relationship rel : data.createdRelationships())
                {

                    AbstractRelationship relationship = relFactory.createRelationship(securityContext, rel);

                    hasError |= graphObjectCreated(securityContext, transactionKey, errorBuffer, relationship);

                    createdRels.add(relationship);

                }

                // 4: notify listeners of relationship deletion
                for (Relationship rel : data.deletedRelationships())
                {

                    AbstractRelationship relationship = relFactory.createRelationship(securityContext, rel);

//                                      AbstractRelationship relationship = relFactory.createRelationship(securityContext, removedRelProperties.get(rel));
                    hasError |= graphObjectDeleted(securityContext, transactionKey, errorBuffer, relationship, removedRelProperties.get(rel));

                    deletedRels.add(relationship);

                }

                // 5: notify listeners of node deletion
                for (Node node : data.deletedNodes())
                {

                    hasError |= graphObjectDeleted(securityContext, transactionKey, errorBuffer, null, removedNodeProperties.get(node));

                }

                // 6: validate property modifications and
                // notify listeners of property modifications
                for (PropertyEntry<Node> entry : data.assignedNodeProperties())
                {

                    AbstractNode entity = nodeFactory.createNode(securityContext, entry.entity());
                    String key = entry.key();
                    Object value = entry.value();

                    // iterate over validators
                    Set<PropertyValidator> validators = EntityContext.getPropertyValidators(securityContext, entity.getClass(), key);

                    if (validators != null)
                    {

                        for (PropertyValidator validator : validators)
                        {

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

                        }

                    }

                    hasError |= propertyModified(securityContext, transactionKey, errorBuffer, entity, key, entry.previouslyCommitedValue(), value);

                    // after successful validation, add node to index to make uniqueness constraints work
                    indexNodeCommand.execute(entity, key);
                    modifiedNodes.add(entity);

                }

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

                    AbstractRelationship entity = relFactory.createRelationship(securityContext, entry.entity());
                    String key = entry.key();
                    Object value = entry.value();

                    // iterate over validators
                    Set<PropertyValidator> validators = EntityContext.getPropertyValidators(securityContext, entity.getClass(), key);

                    if (validators != null)
                    {

                        for (PropertyValidator validator : validators)
                        {

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

                        }

                    }

                    hasError |= propertyModified(securityContext, transactionKey, errorBuffer, entity, key, entry.previouslyCommitedValue(), value);

                    // after successful validation, add relationship to index to make uniqueness constraints work
                    indexRelationshipCommand.execute(entity, key);
                    modifiedRels.add(entity);

                }

                // 7: notify listeners of modified nodes (to check for non-existing properties etc)
                for (AbstractNode node : modifiedNodes)
                {

                    // only send UPDATE if node was not created in this transaction
                    if (!createdNodes.contains(node))
                    {

                        hasError |= graphObjectModified(securityContext, transactionKey, errorBuffer, node);

                    }
                    else
                    {

                        indexNodeCommand.execute(node);

                    }
                }

                for (AbstractRelationship rel : modifiedRels)
                {

                    // only send UPDATE if node was not created in this transaction
                    if (!createdRels.contains(rel))
                    {

                        hasError |= graphObjectModified(securityContext, transactionKey, errorBuffer, rel);

                    }
                    else
                    {

                        indexRelationshipCommand.execute(rel);

                    }
                }

                for (AbstractNode node : createdNodes)
                {

                    indexNodeCommand.execute(node);

                }

                for (AbstractRelationship rel : createdRels)
                {

                    indexRelationshipCommand.execute(rel);

                }

                // notify listeners of commit
                hasError |= commit(securityContext, transactionKey, errorBuffer);

                if (hasError)
                {

                    throw new FrameworkException(422, errorBuffer);

                }

            }
            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);
            }
        }

        @Override
        public boolean begin(SecurityContext securityContext, long transactionKey, ErrorBuffer errorBuffer)
        {

            boolean hasError = false;

            for (VetoableGraphObjectListener listener : modificationListeners)
            {

                hasError |= listener.begin(securityContext, transactionKey, errorBuffer);

            }

            return hasError;
        }

        @Override
        public boolean commit(SecurityContext securityContext, long transactionKey, ErrorBuffer errorBuffer)
        {

            boolean hasError = false;

            for (VetoableGraphObjectListener listener : modificationListeners)
            {

                hasError |= listener.commit(securityContext, transactionKey, errorBuffer);

            }

            return hasError;
        }

        @Override
        public boolean rollback(SecurityContext securityContext, long transactionKey, ErrorBuffer errorBuffer)
        {

            boolean hasError = false;

            for (VetoableGraphObjectListener listener : modificationListeners)
            {

                hasError |= listener.rollback(securityContext, transactionKey, errorBuffer);

            }

            return hasError;
        }

        @Override
        public boolean propertyModified(SecurityContext securityContext, long transactionKey, ErrorBuffer errorBuffer, GraphObject entity, String key, Object oldValue, Object newValue)
        {

            boolean hasError = false;

            for (VetoableGraphObjectListener listener : modificationListeners)
            {

                hasError |= listener.propertyModified(securityContext, transactionKey, errorBuffer, entity, key, oldValue, newValue);

            }

            return hasError;
        }

        @Override
        public boolean graphObjectCreated(SecurityContext securityContext, long transactionKey, ErrorBuffer errorBuffer, GraphObject graphObject)
        {

            boolean hasError = false;

            for (VetoableGraphObjectListener listener : modificationListeners)
            {

                hasError |= listener.graphObjectCreated(securityContext, transactionKey, errorBuffer, graphObject);

            }

            return hasError;
        }

        @Override
        public boolean graphObjectModified(SecurityContext securityContext, long transactionKey, ErrorBuffer errorBuffer, GraphObject graphObject)
        {

            boolean hasError = false;

            for (VetoableGraphObjectListener listener : modificationListeners)
            {

                hasError |= listener.graphObjectModified(securityContext, transactionKey, errorBuffer, graphObject);

            }

            return hasError;
        }

        @Override
        public boolean wasVisited(List<GraphObject> traversedNodes, long transactionKey, ErrorBuffer errorBuffer, SecurityContext securityContext)
        {

            boolean hasError = false;

            for (VetoableGraphObjectListener listener : modificationListeners)
            {

                hasError |= listener.wasVisited(traversedNodes, transactionKey, errorBuffer, securityContext);

            }

            return hasError;
        }

        @Override
        public boolean graphObjectDeleted(SecurityContext securityContext, long transactionKey, ErrorBuffer errorBuffer, GraphObject graphObject, Map<String, Object> properties)
        {

            boolean hasError = false;

            for (VetoableGraphObjectListener listener : modificationListeners)
            {

                hasError |= listener.graphObjectDeleted(securityContext, transactionKey, errorBuffer, graphObject, properties);

            }

            return hasError;
        }
    }

    // </editor-fold>
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值