Package com.breezejs.hib

Source Code of com.breezejs.hib.RelationshipFixer

package com.breezejs.hib;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.EntityMode;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.type.ComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.Type;

import com.breezejs.save.EntityInfo;
import com.breezejs.save.EntityState;


/**
* Utility class for re-establishing the relationships between entities prior to saving them in Hibernate.
* Breeze requires many-to-one relationships to have properties both the related entity and its ID, and it
* sends only the ID in the save bundle.  To make it work with Hibernate, we map the <code>many-to-one</code> entity, and map the
* foreign key ID with <code> insert="false" update="false" </code>, so the <code>many-to-one</code> entity must
* be populated in order for the foreign key value to be saved in the DB.  To work
* around this problem, this class uses the IDs sent by Breeze to re-connect the related entities.
* @author Steve
*/
public class RelationshipFixer {
    private Map<Class, List<EntityInfo>> saveMap;
    private Map<String, String> fkMap;
    private Session session;
    private SessionFactory sessionFactory;
    private List<EntityInfo> saveOrder;
    private List<EntityInfo> deleteOrder;
    private Map<EntityInfo, List<EntityInfo>> dependencyGraph;
    private boolean removeMode;
   
    /**
     * Create new instance with the given saveMap and fkMap.  Since the saveMap is unique per save,
     * this instance will be useful for processing one entire save bundle only.
     * @param saveMap Map of entity types -> entity instances to save.  This is provided by Breeze in the SaveChanges call.
     * @param fkMap Map of relationship name -> foreign key name.  This is built in the MetadataBuilder class.
     * @param session Hibernate session that will save the entities
     */
  public RelationshipFixer(Map<Class, List<EntityInfo>> saveMap, Map<String, String> fkMap, Session session) {
    super();
    this.saveMap = saveMap;
    this.fkMap = fkMap;
    this.session = session;
    this.sessionFactory = session.getSessionFactory();
        this.dependencyGraph = new HashMap<EntityInfo, List<EntityInfo>>();
  }

 
  /**
   * Connect the related entities in the saveMap to other entities.  If the related entities
   * are not in the saveMap, they are loaded from the session.
   * @return The list of entities in the order they should be save, according to their relationships.
   */
    public List<EntityInfo> fixupRelationships()
    {
        this.removeMode = false;
        processRelationships();
      return sortDependencies();
    }
   
    /**
     * Remove the navigations between entities in the saveMap.
     * This flattens the JSON result so Breeze can handle it.
     */
    public void removeRelationships()
    {
        this.removeMode = true;
        processRelationships();
    }
   
    /**
     * Add the relationship to the dependencyGraph
     * @param child Entity that depends on parent (e.g. has a many-to-one relationship to parent)
     * @param parent Entity that child depends on (e.g. one parent has one-to-many children)
     * @param removeReverse True to find and remove the reverse relationship.  Used for handling one-to-ones.
     */
    private void addToGraph(EntityInfo child, EntityInfo parent, boolean removeReverse)
    {
        List<EntityInfo> list = dependencyGraph.get(child);
        if (list == null) {
            list = new ArrayList<EntityInfo>(5);
            dependencyGraph.put(child, list);
        }
        if (parent != null) list.add(parent);

        if (removeReverse) {
            List<EntityInfo> parentList = dependencyGraph.get(parent);
            if (parentList != null) {
                parentList.remove(child);
            }
        }
    }

    /**
     * Sort the entries in the dependency graph according to their dependencies.
     * @return the sorted list
     */
    private List<EntityInfo> sortDependencies()
    {
        saveOrder = new ArrayList<EntityInfo>();
        deleteOrder = new ArrayList<EntityInfo>();
        for (EntityInfo entityInfo : dependencyGraph.keySet()) {
            addToSaveOrder(entityInfo, 0);
        }
        Collections.reverse(deleteOrder);
        saveOrder.addAll(deleteOrder);
        return saveOrder;
    }

    /**
     * Recursively add entities to the saveOrder or deleteOrder according to their dependencies
     * @param entityInfo Entity to be added.  Its dependencies will be added depth-first.
     * @param depth prevents infinite recursion in case of cyclic dependencies
     */
    private void addToSaveOrder(EntityInfo entityInfo, int depth)
    {
        if (saveOrder.contains(entityInfo)) return;
        if (deleteOrder.contains(entityInfo)) return;
        if (depth > 10) return;

        List<EntityInfo> dependencies = dependencyGraph.get(entityInfo);
        for (EntityInfo dep : dependencies) {
            addToSaveOrder(dep, depth + 1);
        }

        if (entityInfo.entityState == EntityState.Deleted)
            deleteOrder.add(entityInfo);
        else
            saveOrder.add(entityInfo);
    }
   
   
    /**
     * Add or remove the entity relationships according to the current removeMode.
     */
    private void processRelationships()
    {
      for (Entry<Class, List<EntityInfo>> entry : saveMap.entrySet()) {
       
            Class entityType = entry.getKey();
            ClassMetadata classMeta = sessionFactory.getClassMetadata(entityType);

            for (EntityInfo entityInfo : entry.getValue())
            {
                addToGraph(entityInfo, null, false); // make sure every entity is in the graph
                fixupRelationships(entityInfo, classMeta);
            }
        }     
    }
 
    /**
     * Connect the related entities based on the foreign key values.
     * Note that this may cause related entities to be loaded from the DB if they are not already in the session.
     * @param entityInfo Entity that will be saved
     * @param meta Metadata about the entity type
     */
    private void fixupRelationships(EntityInfo entityInfo, ClassMetadata meta)
    {
        String[] propNames = meta.getPropertyNames();
        Type[] propTypes = meta.getPropertyTypes();
       
        Type propType = meta.getIdentifierType();
        if (propType != null)
        {
            if (propType.isAssociationType() && propType.isEntityType())
            {
                fixupRelationship(meta.getIdentifierPropertyName(), (EntityType)propType, entityInfo, meta);
            }
            else if (propType.isComponentType())
            {
                fixupComponentRelationships(meta.getIdentifierPropertyName(), (ComponentType)propType, entityInfo, meta);
            }
        }

        for (int i = 0; i < propNames.length; i++)
        {
            propType = propTypes[i];
            if (propType.isAssociationType() && propType.isEntityType())
            {
                fixupRelationship(propNames[i], (EntityType)propTypes[i], entityInfo, meta);
            }
            else if (propType.isComponentType())
            {
                fixupComponentRelationships(propNames[i], (ComponentType)propType, entityInfo, meta);
            }
        }
    }
   
    /**
     * Connect the related entities based on the foreign key values found in a component type.
     * This updates the values of the component's properties.
     * @param propName Name of the (component) property of the entity.  May be null if the property is the entity's identifier.
     * @param compType Type of the component
     * @param entityInfo Breeze EntityInfo
     * @param meta Metadata for the entity class
     */
    private void fixupComponentRelationships(String propName, ComponentType compType, EntityInfo entityInfo, ClassMetadata meta)
    {
        String[] compPropNames = compType.getPropertyNames();
        Type[] compPropTypes = compType.getSubtypes();
        Object component = null;
        Object[] compValues = null;
        boolean isChanged = false;
        for (int j = 0; j < compPropNames.length; j++)
        {
            Type compPropType = compPropTypes[j];
            if (compPropType.isAssociationType() && compPropType.isEntityType())
            {
                if (compValues == null)
                {
                    // get the value of the component's subproperties
                    component = getPropertyValue(meta, entityInfo.entity, propName);
                    compValues = compType.getPropertyValues(component, EntityMode.POJO);
                }
                if (compValues[j] == null)
                {
                    // the related entity is null
                    Object relatedEntity = getRelatedEntity(compPropNames[j], (EntityType)compPropType, entityInfo, meta);
                    if (relatedEntity != null)
                    {
                        compValues[j] = relatedEntity;
                        isChanged = true;
                    }
                }
                else if (removeMode)
                {
                    // remove the relationship
                    compValues[j] = null;
                    isChanged = true;
                }
            }
        }
        if (isChanged)
        {
            compType.setPropertyValues(component, compValues, EntityMode.POJO);
        }

    }
   
    /**
     * Set an association value based on the value of the foreign key.  This updates the property of the entity.
     * @param propName Name of the navigation/association property of the entity, e.g. "Customer".  May be null if the property is the entity's identifier.
     * @param propType Type of the property
     * @param entityInfo Breeze EntityInfo
     * @param meta Metadata for the entity class
     */
    private void fixupRelationship(String propName, EntityType propType, EntityInfo entityInfo, ClassMetadata meta)
    {
        Object entity = entityInfo.entity;
        if (removeMode)
        {
            meta.setPropertyValue(entity, propName, null);
            return;
        }
        Object relatedEntity = getPropertyValue(meta, entity, propName);
        if (relatedEntity != null) return;    // entities are already connected

        relatedEntity = getRelatedEntity(propName, propType, entityInfo, meta);

        if (relatedEntity != null)
            meta.setPropertyValue(entity, propName, relatedEntity);
    }
   
    /**
     * Get a related entity based on the value of the foreign key.  Attempts to find the related entity in the
     * saveMap; if its not found there, it is loaded via the Session (which should create a proxy, not actually load
     * the entity from the database).
     * Related entities are Promoted in the saveOrder according to their state.
     * @param propName Name of the navigation/association property of the entity, e.g. "Customer".  May be null if the property is the entity's identifier.
     * @param propType Type of the property
     * @param entityInfo Breeze EntityInfo
     * @param meta Metadata for the entity class
     * @return
     */
    private Object getRelatedEntity(String propName, EntityType propType, EntityInfo entityInfo, ClassMetadata meta)
    {
      Object relatedEntity = null;
      String foreignKeyName = findForeignKey(propName, meta);
        Object id = getForeignKeyValue(entityInfo, meta, foreignKeyName);

        if (id != null)
        {
            EntityInfo relatedEntityInfo = findInSaveMap(propType.getReturnedClass(), id);

            if (relatedEntityInfo == null) {
              EntityState state = entityInfo.entityState;
//              if (state == EntityState.Added || state == EntityState.Modified || (state == EntityState.Deleted
//                  && propType.getForeignKeyDirection() != ForeignKeyDirection.FOREIGN_KEY_TO_PARENT)) {
              if (state != EntityState.Deleted || propType.getForeignKeyDirection() != ForeignKeyDirection.FOREIGN_KEY_TO_PARENT) {
                  String relatedEntityName = propType.getName();
                    relatedEntity = session.load(relatedEntityName, (Serializable) id, LockOptions.NONE);
              }
            } else {
              boolean removeReverseRelationship = propType.useLHSPrimaryKey();
                addToGraph(entityInfo, relatedEntityInfo, removeReverseRelationship);
                relatedEntity = relatedEntityInfo.entity;
            }
        }
        return relatedEntity;
    }
   
    /**
     * Find a foreign key matching the given property, by looking in the fkMap.
     * The property may be defined on the class or a superclass, so this function calls itself recursively.
     * @param propName Name of the property e.g. "Product"
     * @param meta Class metadata, for traversing the class hierarchy
     * @return The name of the foreign key, e.g. "ProductID"
     */
    private String findForeignKey(String propName, ClassMetadata meta)
    {
        String relKey = meta.getEntityName() + '.' + propName;
        if (fkMap.containsKey(relKey))
        {
            return fkMap.get(relKey);
        }
        else if (meta.isInherited() && meta instanceof AbstractEntityPersister)
        {
            String superEntityName = ((AbstractEntityPersister)meta).getMappedSuperclass();
            ClassMetadata superMeta = sessionFactory.getClassMetadata(superEntityName);
            return findForeignKey(propName, superMeta);
        }
        else
        {
            throw new IllegalArgumentException("Foreign Key '" + relKey + "' could not be found.");
        }
    }
   
    /**
     * Get the value of the foreign key property.  This comes from the entity, but if that value is
     * null, and the entity is deleted, we try to get it from the originalValuesMap.
     * @param entityInfo Breeze EntityInfo
     * @param meta Metadata for the entity class
     * @param foreignKeyName Name of the foreign key property of the entity, e.g. "CustomerID"
     * @return
     */
    private Object getForeignKeyValue(EntityInfo entityInfo, ClassMetadata meta, String foreignKeyName)
    {
        Object entity = entityInfo.entity;
        Object id = null;
        if (foreignKeyName == meta.getIdentifierPropertyName())
            id = meta.getIdentifier(entity, null);
        else if (Arrays.asList(meta.getPropertyNames()).contains(foreignKeyName))
            id = meta.getPropertyValue(entity, foreignKeyName);
        else if (meta.getIdentifierType().isComponentType())
        {
            // compound key
          ComponentType compType = (ComponentType) meta.getIdentifierType();
          int index = Arrays.asList(compType.getPropertyNames()).indexOf(foreignKeyName);
            if (index >= 0)
            {
                Object idComp = meta.getIdentifier(entity, null);
                id = compType.getPropertyValue(idComp, index, EntityMode.POJO);
            }
        }

        if (id == null && entityInfo.entityState == EntityState.Deleted)
        {
            id = entityInfo.originalValuesMap.get(foreignKeyName);
        }
        return id;
    }
   
    /**
     * Return the property value for the given entity.
     * @param meta
     * @param entity
     * @param propName If null, the identifier property will be returned.
     * @return
     */
    private Object getPropertyValue(ClassMetadata meta, Object entity, String propName)
    {
        if (propName == null || propName == meta.getIdentifierPropertyName())
            return meta.getIdentifier(entity, null);
        else
            return meta.getPropertyValue(entity, propName);
    }
   
    /**
     * Find the matching entity in the saveMap.  This is for relationship fixup.
     * @param entityType Type of entity, e.g. Order
     * @param entityId Key value of the entity
     * @return The entity, or null if not found
     */
    private EntityInfo findInSaveMap(Class entityType, Object entityId)
    {
        String entityIdString = entityId.toString();
        ArrayList<EntityInfo> entityInfoList = new ArrayList<EntityInfo>();
        for (Class c : saveMap.keySet()) {
          if (entityType.isAssignableFrom(c)) {
            entityInfoList.addAll(saveMap.get(c));
          }
        }
        if (entityInfoList != null && entityInfoList.size() != 0)
        {
            ClassMetadata meta = sessionFactory.getClassMetadata(entityType);
            for (EntityInfo entityInfo : entityInfoList)
            {
                Object entity = entityInfo.entity;
                Object id = meta.getIdentifier(entity, null);
                if (id != null && entityIdString.equals(id.toString())) return entityInfo;
            }
        }
        return null;
    }
   
}
TOP

Related Classes of com.breezejs.hib.RelationshipFixer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.