package er.extensions.partials; import java.util.Enumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.eoaccess.EOAttribute; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOModel; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eoaccess.EOProperty; import com.webobjects.eoaccess.EORelationship; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import com.webobjects.foundation.NSMutableSet; import com.webobjects.foundation.NSNotification; import com.webobjects.foundation.NSNotificationCenter; import com.webobjects.foundation.NSSelector; import com.webobjects.foundation._NSUtilities; import er.extensions.eof.ERXConstant; import er.extensions.eof.ERXEntityClassDescription; import er.extensions.eof.ERXModelGroup; import er.extensions.foundation.ERXProperties; /** * <p> * For overview information on partials, read the {@code package.html} in * {@code er.extensions.partials}. * </p> * * <p> * {@code ERXPartialInitializer} is registered at startup and is responsible for * merging partial entities together into a single entity. * </p> * * @property er.extensions.partials.enabled * @author mschrag * @author jtabert */ public class ERXPartialInitializer { private static final Logger log = LoggerFactory.getLogger(ERXModelGroup.class); private static final ERXPartialInitializer _initializer = new ERXPartialInitializer(); private NSMutableDictionary<EOEntity, NSMutableArray<Class<ERXPartial>>> _partialsForEntity = new NSMutableDictionary<EOEntity, NSMutableArray<Class<ERXPartial>>>(); public static ERXPartialInitializer initializer() { return _initializer; } public static void registerModelGroupListener() { if (ERXProperties.booleanForKeyWithDefault("er.extensions.partials.enabled", false)) { NSNotificationCenter.defaultCenter().addObserver(_initializer, new NSSelector("modelGroupAdded", ERXConstant.NotificationClassArray), ERXModelGroup.ModelGroupAddedNotification, null); // NSNotificationCenter.defaultCenter().addObserver(_initializer, // new NSSelector("workaroundStupidLoadingProblem", // ERXConstant.NotificationClassArray), // WOApplication.ApplicationDidFinishLaunchingNotification, null); } } public void workaroundClassDescriptionResetProblem() { if (ERXProperties.booleanForKeyWithDefault("er.extensions.partials.enabled", false)) { for (EOEntity partialEntity : _partialsForEntity.keySet()) { ERXEntityClassDescription ecd = (ERXEntityClassDescription) partialEntity.classDescriptionForInstances(); for (Class<ERXPartial> partialClass : _partialsForEntity.objectForKey(partialEntity)) { ecd._addPartialClass(partialClass); } } } } public void modelGroupAdded(NSNotification notification) { ERXModelGroup modelGroup = (ERXModelGroup) notification.object(); initializePartialEntities(modelGroup); } @SuppressWarnings({ "unchecked", "cast" }) public void initializePartialEntities(EOModelGroup modelGroup) { NSMutableDictionary<EOEntity, EOEntity> baseForPartial = new NSMutableDictionary<>(); Enumeration modelsEnum = modelGroup.models().objectEnumerator(); while (modelsEnum.hasMoreElements()) { EOModel model = (EOModel) modelsEnum.nextElement(); // TODO: merge userInfo from model Enumeration entitiesEnum = model.entities().objectEnumerator(); while (entitiesEnum.hasMoreElements()) { EOEntity partialExtensionEntity = (EOEntity) entitiesEnum.nextElement(); NSDictionary userInfo = partialExtensionEntity.userInfo(); NSDictionary entityModelerDictionary = (NSDictionary) userInfo.objectForKey("_EntityModeler"); if (entityModelerDictionary != null) { String partialEntityName = (String) entityModelerDictionary.objectForKey("partialEntity"); if (partialEntityName != null) { EOEntity partialEntity = modelGroup.entityNamed(partialEntityName); if (partialEntity == null) { throw new IllegalArgumentException("The entity '" + partialExtensionEntity.name() + "' claimed to be a partialEntity for the entity '" + partialEntityName + "', but there is no entity of that name."); } Enumeration partialAttributes = partialExtensionEntity.attributes().objectEnumerator(); while (partialAttributes.hasMoreElements()) { EOAttribute partialAttribute = (EOAttribute) partialAttributes.nextElement(); if (partialEntity.attributeNamed(partialAttribute.name()) == null) { NSMutableDictionary<String, Object> attributePropertyList = new NSMutableDictionary<>(); partialAttribute.encodeIntoPropertyList(attributePropertyList); String factoryMethodArgumentType = (String) attributePropertyList.objectForKey("factoryMethodArgumentType"); // OFFICIALLY THE DUMBEST DAMN THING I'VE EVER SEEN if ("EOFactoryMethodArgumentIsString".equals(factoryMethodArgumentType)) { attributePropertyList.setObjectForKey("EOFactoryMethodArgumentIsNSString", "factoryMethodArgumentType"); } EOAttribute primaryAttribute = new EOAttribute(attributePropertyList, partialEntity); primaryAttribute.awakeWithPropertyList(attributePropertyList); partialEntity.addAttribute(primaryAttribute); // check if the attribute is a class property if (!partialExtensionEntity.classPropertyNames().contains(partialAttribute.name())) { EOProperty p = partialEntity.propertyNamed(partialAttribute.name()); if (p != null) { partialEntity.classProperties().remove(p); log.debug("Removing partial attribute {}.{} from {}.classProperties because it is not defined as class property.", partialExtensionEntity.name(), partialAttribute.name(), partialEntity.name()); } } // check if the attribute is used for locking if (!partialExtensionEntity.attributesUsedForLocking().contains(partialAttribute)) { EOAttribute a = partialEntity.attributeNamed(partialAttribute.name()); if (a != null) { partialEntity.attributesUsedForLocking().remove(a); log.debug("Removing partial attribute {}.{} from {} attributesUsedForLocking because it is not defined as locking attribute.", partialExtensionEntity.name(), partialAttribute.name(), partialEntity.name()); } } } else { // TODO: merge userInfo from attribute log.debug("Skipping partial attribute {}.{} because {} already has an attribute of the same name.", partialExtensionEntity.name(), partialAttribute.name(), partialEntity.name()); } } Enumeration partialRelationships = partialExtensionEntity.relationships().objectEnumerator(); while (partialRelationships.hasMoreElements()) { EORelationship partialRelationship = (EORelationship) partialRelationships.nextElement(); if (partialEntity.relationshipNamed(partialRelationship.name()) == null) { NSMutableDictionary<String, Object> relationshipPropertyList = new NSMutableDictionary<>(); partialRelationship.encodeIntoPropertyList(relationshipPropertyList); EORelationship primaryRelationship = new EORelationship(relationshipPropertyList, partialEntity); primaryRelationship.awakeWithPropertyList(relationshipPropertyList); partialEntity.addRelationship(primaryRelationship); // check if the relationship is a class property if (!partialExtensionEntity.classPropertyNames().contains(partialRelationship.name())) { EOProperty p = partialEntity.propertyNamed(partialRelationship.name()); if (p != null) { partialEntity.classProperties().remove(p); log.debug("Removing partial relationship {}.{} from {} classProperties because it is not defined as class property.", partialExtensionEntity.name(), partialRelationship.name(), partialEntity.name()); } } } else { // TODO: merge userInfo from relationship log.debug("Skipping partial relationship {}.{} because {} already has a relationship of the same name.", partialExtensionEntity.name(), partialRelationship.name(), partialEntity.name()); } } NSMutableArray<Class<ERXPartial>> partialsForEntity = _partialsForEntity.objectForKey(partialEntity); if (partialsForEntity == null) { partialsForEntity = new NSMutableArray<Class<ERXPartial>>(); _partialsForEntity.setObjectForKey(partialsForEntity, partialEntity); } Class<ERXPartial> partialClass = (Class<ERXPartial>) _NSUtilities.classWithName(partialExtensionEntity.className()); ERXEntityClassDescription ecd = (ERXEntityClassDescription) partialEntity.classDescriptionForInstances(); ecd._addPartialClass(partialClass); partialsForEntity.addObject(partialClass); baseForPartial.setObjectForKey(partialEntity, partialExtensionEntity); } } } } NSMutableSet<EOEntity> convertedEntities = new NSMutableSet<>(); modelsEnum = modelGroup.models().objectEnumerator(); while (modelsEnum.hasMoreElements()) { EOModel model = (EOModel) modelsEnum.nextElement(); Enumeration entitiesEnum = model.entities().objectEnumerator(); while (entitiesEnum.hasMoreElements()) { EOEntity entity = (EOEntity) entitiesEnum.nextElement(); convertEntityPartialReferences(entity, baseForPartial, convertedEntities); } } for (EOEntity partialExtensionEntity : baseForPartial.allKeys()) { partialExtensionEntity.model().removeEntity(partialExtensionEntity); } } protected void convertEntityPartialReferences(EOEntity entity, NSMutableDictionary<EOEntity, EOEntity> baseForPartial, NSMutableSet<EOEntity> convertedEntities) { if (!convertedEntities.containsObject(entity)) { convertedEntities.addObject(entity); Enumeration relationships = entity.relationships().immutableClone().objectEnumerator(); while (relationships.hasMoreElements()) { EORelationship relationship = (EORelationship) relationships.nextElement(); convertRelationshipPartialReferences(entity, relationship, baseForPartial, convertedEntities); } } } @SuppressWarnings("unchecked") protected void convertRelationshipPartialReferences(EOEntity entity, EORelationship relationship, NSMutableDictionary<EOEntity, EOEntity> baseForPartial, NSMutableSet<EOEntity> convertedEntities) { EOEntity destinationEntity = relationship.destinationEntity(); EOEntity baseEntity = baseForPartial.objectForKey(destinationEntity); if (baseEntity != null) { if (relationship.isFlattened()) { for (EORelationship componentRelationship : (NSArray<EORelationship>)relationship.componentRelationships()) { EOEntity componentEntity = componentRelationship.destinationEntity(); if (componentEntity == entity) { convertRelationshipPartialReferences(entity, componentRelationship, baseForPartial, convertedEntities); } else { convertEntityPartialReferences(componentEntity, baseForPartial, convertedEntities); } } } NSMutableDictionary<String, Object> relationshipPropertyList = new NSMutableDictionary<>(); relationship.encodeIntoPropertyList(relationshipPropertyList); relationshipPropertyList.setObjectForKey(baseEntity.name(), "destination"); EORelationship primaryRelationship = new EORelationship(relationshipPropertyList, entity); primaryRelationship.awakeWithPropertyList(relationshipPropertyList); // MS: This looks silly, but 5.4 has a bug where the relationship dictionary isn't necessarily initialized at this point, so we want to force it to load entity.relationshipNamed(relationship.name()); entity.removeRelationship(relationship); entity.addRelationship(primaryRelationship); } } }