package er.extensions.eof;
import java.util.Enumeration;
import org.apache.log4j.Logger;
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 Logger logger = Logger.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() {
logger.debug("Building dependency list");
NSMutableDictionary dependencyList = new NSMutableDictionary(allEntities().count());
for (Enumeration entityEnum = allEntities().objectEnumerator(); entityEnum.hasMoreElements();) {
EOEntity entity = (EOEntity) entityEnum.nextElement();
logger.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();
logger.trace("Recording dependency on " + destinationEntity.name());
entitiesDependentOn(dependencyList, destinationEntity).addObject(entity.name());
}
else {
logger.trace("Ignoring, is not FK relationship or vertical inheritance parent");
}
}
}
logger.debug("Finished building dependency list");
if (logger.isTraceEnabled()) {
for (int i = 0; i < allEntities().count(); i++) {
EOEntity entity = allEntities().objectAtIndex(i);
logger.trace("Entity " + entity.name() + " is referenced by " + 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) {
logger.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())) {
logger.trace("Ignoring: reflexive relationship");
return false;
}
if ( ! ERXArrayUtilities.arraysAreIdenticalSets(relationship.destinationAttributes(),
relationship.destinationEntity().primaryKeyAttributes()) ) {
logger.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())) {
logger.trace("Is vertical inheritance PK to PKconstraint");
return true;
}
// Bug? Do these need to be included?
logger.trace("No FK constraint: Is PK to PK");
return false;
}
logger.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;
}
}