package er.extensions.eof; import java.util.Enumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eoaccess.EORelationship; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableDictionary; import com.webobjects.foundation.NSMutableSet; import er.extensions.foundation.ERXArrayUtilities; /** * Creates ordering based on foreign key dependencies. * * @author chill */ public class ERXEntityFKConstraintOrder extends ERXEntityOrder { private static final Logger log = LoggerFactory.getLogger(ERXEntityFKConstraintOrder.class); /** * Designated constructor for implementing classes. * * @param modelGroup EOModelGroup to get list of all entities from */ public ERXEntityFKConstraintOrder(EOModelGroup modelGroup) { super(modelGroup); } /** * Convenience constructor for implementing classes. Uses <code>EOModelGroup.defaultGroup()</code>. */ public ERXEntityFKConstraintOrder() { super(); } /** * Processes the list of entities, creating the ordering dictionary based on foreign key constraints. * * @return a dictionary keyed on dependencyKeyFor(EOEntity) */ @Override protected NSDictionary dependenciesByEntity() { log.debug("Building dependency list"); NSMutableDictionary dependencyList = new NSMutableDictionary(allEntities().count()); for (Enumeration entityEnum = allEntities().objectEnumerator(); entityEnum.hasMoreElements();) { EOEntity entity = (EOEntity) entityEnum.nextElement(); log.trace("Finding dependencies of {}", entity.name()); for (Enumeration relationshipEnum = entity.relationships().objectEnumerator(); relationshipEnum.hasMoreElements();) { EORelationship relationship = (EORelationship) relationshipEnum.nextElement(); if (hasForeignKeyConstraint(relationship)) { EOEntity destinationEntity = relationship.destinationEntity(); log.trace("Recording dependency on {}", destinationEntity.name()); entitiesDependentOn(dependencyList, destinationEntity).addObject(entity.name()); } else { log.trace("Ignoring, is not FK relationship or vertical inheritance parent"); } } } log.debug("Finished building dependency list"); if (log.isTraceEnabled()) { for (int i = 0; i < allEntities().count(); i++) { EOEntity entity = allEntities().objectAtIndex(i); log.trace("Entity {} is referenced by {}", entity.name(), entitiesDependentOn(dependencyList, entity)); } } return dependencyList; } /** * @param relationship EORelationship to test * @return <code>true</code> if relationship models a relation that will have a foreign key constraint in the database */ protected boolean hasForeignKeyConstraint(EORelationship relationship) { log.trace("Examining relationshp {}", relationship.name()); // Reflexive relationships (circular dependencies) can't be accommodated by entity ordering, // these require ordering within the operations for an entity. Check the externalName() rather than // entity name so that it will handle relationships to a super or subclass in a Single-Table inheritance structure if (relationship.entity().externalName() != null && relationship.entity().externalName().equals(relationship.destinationEntity().externalName())) { log.trace("Ignoring: reflexive relationship"); return false; } if ( ! ERXArrayUtilities.arraysAreIdenticalSets(relationship.destinationAttributes(), relationship.destinationEntity().primaryKeyAttributes()) ) { log.trace("No FK constraint: found non-PK attributes in destination"); return false; } // Primary key to primary key relationships are excluded. else if (ERXArrayUtilities.arraysAreIdenticalSets(relationship.sourceAttributes(), relationship.entity().primaryKeyAttributes()) ) { // PK - PK relationships for vertical inheritance (child to parent) also need to be considered in ordering if (relationship.destinationEntity().equals(relationship.entity().parentEntity())) { log.trace("Is vertical inheritance PK to PKconstraint"); return true; } // Bug? Do these need to be included? log.trace("No FK constraint: Is PK to PK"); return false; } log.trace("Is FK constraint"); return true; } /** * This implementation returns <code>entity.externalName()</code> as the dependcy is actually on tables not EOEntities * . * @param entity EOEntity to return key into dependency dictionary for * * @return key for <code>entity</code> into dependency dictionary returned by <code>dependenciesByEntity()</code> */ @Override protected String dependencyKeyFor(EOEntity entity) { if (entity.externalName() == null) { return "Abstract Dummy Entity"; } return entity.externalName(); } /** * Returns the list of the names of the entities that reference (depend on) * this entity. This list is populated by <code>builddependencyList()</code>. * If <code>builddependencyList()</code> has not finished executing, the * list returned by this method may not be complete. * * @param dependencies * list of dependencies being built by * <code>builddependencyList()</code> * @param entity * EOEntity to return list of referencing entities for * @return list of names of entities previously recorded as referencing this * entity */ protected NSMutableSet entitiesDependentOn(NSMutableDictionary dependencies, EOEntity entity) { NSMutableSet referencingEntities = (NSMutableSet) dependencies.objectForKey(dependencyKeyFor(entity)); if (referencingEntities == null) { referencingEntities = new NSMutableSet(); dependencies.setObjectForKey(referencingEntities, dependencyKeyFor(entity)); } return referencingEntities; } }