package er.extensions.eof; import java.util.concurrent.ConcurrentHashMap; import com.webobjects.eocontrol.EOAndQualifier; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOQualifier; import com.webobjects.eocontrol.EOSortOrdering; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSMutableArray; /** * A class than is composited into an EO to provide common toMany functionality * for the case where the toMany cannot be modeled in EOF due to the unusually * large size possibilities of the toMany relationship. * <p> * This class is for simple to many relationships, has not been tested on * flattened toMany relationships having a join table, aka "many-to-many" relationships. * <p> * Usage: Lazily create a private instance of this inside an EO passing in the * appropriate constructor parameters and then implement cover methods similar * to those that would have been available thru the normal Entity templates * calling the corresponding methods of this class. * <p> * For example, you might do this in an entity named CTMediaTemplate that formerly had a 'messages' * relationship to CTMessage but due to the huge size of the toMany impacting performance, the toMany side of the * relationship had to be deleted from the EOModel: * * <pre><code> private ERXUnmodeledToManyRelationship<CTMediaTemplate, CTMessage> _messagesRelationship; // Lazily initialize the helper class public ERXUnmodeledToManyRelationship<CTMediaTemplate, CTMessage> messagesRelationship() { if (_messagesRelationship == null) { _messagesRelationship = new ERXUnmodeledToManyRelationship<CTMediaTemplate, CTMessage>(this, CTMessage.ENTITY_NAME, CTMessage.XKEY_MEDIA_TEMPLATE); } return _messagesRelationship; } public Integer countMessages() { return messagesRelationship().countObjects(); } public EOQualifier qualifierForMessages() { return messagesRelationship().qualifierForObjects(); } public NSArray<CTMessage> messages() { return messagesRelationship().objects(); } public ERXFetchSpecification<CTMessage> fetchSpecificationForMessages() { return messagesRelationship().fetchSpecificationForObjects(); } public NSArray<CTMessage> messages(EOQualifier qualifier) { return messagesRelationship().objects(qualifier); } public NSArray<CTMessage> messages(EOQualifier qualifier, boolean fetch) { return messagesRelationship().objects(qualifier, null, fetch); } public NSArray<CTMessage> messages(EOQualifier qualifier, NSArray<EOSortOrdering> sortOrderings, boolean fetch) { return messagesRelationship().objects(qualifier, sortOrderings, fetch); } public void addToMessagesRelationship(CTMessage object) { messagesRelationship().addToObjectsRelationship(object); } public void removeFromMessagesRelationship(CTMessage object) { messagesRelationship().removeFromObjectsRelationship(object); } public void deleteMessagesRelationship(CTMessage object) { messagesRelationship().deleteObjectRelationship(object); } public void deleteAllMessagesRelationships() { messagesRelationship().deleteAllObjectsRelationships(); } </code></pre> * @author kieran * */ public class ERXUnmodeledToManyRelationship<S extends ERXEnterpriseObject, D extends ERXEnterpriseObject> { private final S sourceObject; private final String destinationEntityName; private final ERXKey<S> reverseRelationshipKey; private final boolean isDeep; // Cache the entityHierarchies rather than fiddle with EOEntities for every instance of this class (once per EO) private static ConcurrentHashMap<String, NSArray<String>> _entityHierarchies = new ConcurrentHashMap<String, NSArray<String>>(); /** * Standard constructor where the destination entity is a single type. Excludes entities that might inherit from the destination entity * * @param sourceObject * @param destinationEntityName * @param reverseRelationshipKey */ public ERXUnmodeledToManyRelationship(S sourceObject, String destinationEntityName, ERXKey<S> reverseRelationshipKey) { this(sourceObject, destinationEntityName, reverseRelationshipKey, false); } /** * A constructor that allows isDeep to be set to true to handle destination entity that is the super class of an inheritance hierarchy. * The inherited entities are included in the methods that return arrays of destination EOEnterpriseObjects. * * @param sourceObject * @param destinationEntityName * @param reverseRelationshipKey * @param isDeep */ public ERXUnmodeledToManyRelationship(S sourceObject, String destinationEntityName, ERXKey<S> reverseRelationshipKey, boolean isDeep) { this.sourceObject = sourceObject; this.destinationEntityName = destinationEntityName; this.reverseRelationshipKey = reverseRelationshipKey; this.isDeep = isDeep; } /** * @return the total count of objects in the relationship */ public Integer countObjects() { int count = 0; // Can only do SQL count if not a new unsaved object if (!sourceObject.isNewObject()) { count += ERXEOControlUtilities.objectCountWithQualifier(sourceObject.editingContext(), destinationEntityName, qualifierForObjects()).intValue(); } // Add count of unsaved related objects count += insertedObjects().count(); return Integer.valueOf(count); } /** * @return the {@link EOQualifier} that qualifies the toMany destination * objects */ public EOQualifier qualifierForObjects() { return reverseRelationshipKey.eq(sourceObject); } /** * @return the related destination objects */ public NSArray<D> objects() { // Grab the unsaved ones NSMutableArray<D> objects = insertedObjects(); // Add the persisted ones form the database objects.addObjectsFromArray(persistedObjects()); // return them all return objects.immutableClone(); } /** * @return the {@link ERXFetchSpecification} that fetches the destination * toMany objects that have been persisted to the database */ public ERXFetchSpecification<D> fetchSpecificationForObjects() { return new ERXFetchSpecification<>(destinationEntityName, qualifierForObjects(), null, false, isDeep, null); } public NSArray<D> objects(EOQualifier qualifier) { return objects(qualifier, null, false); } public NSArray<D> objects(EOQualifier qualifier, boolean fetch) { return objects(qualifier, null, fetch); } public NSArray<D> objects(EOQualifier qualifier, NSArray<EOSortOrdering> sortOrderings, boolean fetch) { NSArray<D> results; if (fetch) { EOQualifier fullQualifier; EOQualifier inverseQualifier = qualifierForObjects(); if (qualifier == null) { fullQualifier = inverseQualifier; } else { NSMutableArray<EOQualifier> qualifiers = new NSMutableArray<>(); qualifiers.addObject(qualifier); qualifiers.addObject(inverseQualifier); fullQualifier = new EOAndQualifier(qualifiers); } ERXFetchSpecification<D> fs = fetchSpecificationForObjects(); fs.setSortOrderings(sortOrderings); fs.setQualifier(fullQualifier); fs.setRefreshesRefetchedObjects(fetch); results = fs.fetchObjects(sourceObject.editingContext()); } else { results = objects(); if (qualifier != null) { results = ERXQ.filtered(results, qualifier); } if (sortOrderings != null) { results = ERXS.sorted(results, sortOrderings); } } return results; } /** * @return the fetched pre-existing objects from the database */ private NSArray<D> persistedObjects() { ERXFetchSpecification<D> fs = fetchSpecificationForObjects(); return fs.fetchObjects(sourceObject.editingContext()); } /** * @return the array of related instances that * are inserted into this editing context, but not yet saved. */ private NSMutableArray<D> insertedObjects() { return ERXEOControlUtilities.insertedObjects(sourceObject.editingContext(), destinationEntityNames(), qualifierForObjects()); } public void addToObjectsRelationship(D object) { object.addObjectToBothSidesOfRelationshipWithKey(sourceObject, reverseRelationshipKey.toString()); } public void addToObjectsRelationship(NSArray<D> objects) { for (D object : objects) { object.addObjectToBothSidesOfRelationshipWithKey(sourceObject, reverseRelationshipKey.toString()); } } public void removeFromObjectsRelationship(D object) { object.removeObjectFromBothSidesOfRelationshipWithKey(sourceObject, reverseRelationshipKey.toString()); } public void removeFromObjectsRelationship(NSArray<D> objects) { for (D object : objects) { object.removeObjectFromBothSidesOfRelationshipWithKey(sourceObject, reverseRelationshipKey.toString()); } } public void removeAllFromObjectsRelationship() { NSArray<D> objects = objects().immutableClone(); for (D object : objects) { removeFromObjectsRelationship(object); } } public void deleteObjectRelationship(D object) { removeFromObjectsRelationship(object); sourceObject.editingContext().deleteObject(object); } public void deleteAllObjectsRelationships() { NSArray<D> objects = objects().immutableClone(); for (D object : objects) { deleteObjectRelationship(object); } } private NSArray<String> _destinationEntityNames; /** @return the destination entity names. If this is an inheritance hierarchy, the subclass entities of the specified entity are included */ private NSArray<String> destinationEntityNames() { if ( _destinationEntityNames == null ) { if (isDeep) { _destinationEntityNames = entityHierarchyNamesForEntityNamed(sourceObject.editingContext(), destinationEntityName); } else { _destinationEntityNames = new NSArray<>(destinationEntityName); } } return _destinationEntityNames; } /** * @param rootEntityName * @return a list of all concrete entity names that inherit from rootEntityName, including rootEntityName itself if it is concrete. */ private static NSArray<String> entityHierarchyNamesForEntityNamed(EOEditingContext ec, String rootEntityName) { NSArray<String> cachedResult = _entityHierarchies.get(rootEntityName); if (cachedResult == null) { cachedResult = ERXEOAccessUtilities.entityHierarchyNamesForEntityNamed(ec, rootEntityName); _entityHierarchies.put(rootEntityName, cachedResult); } return cachedResult; } }