package er.extensions.eof; import java.util.Enumeration; import org.apache.log4j.Logger; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSForwardException; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSNotification; import com.webobjects.foundation.NSNotificationCenter; import com.webobjects.foundation.NSValidation; import er.extensions.ERXExtensions; import er.extensions.foundation.ERXPatcher; import er.extensions.foundation.ERXProperties; import er.extensions.foundation.ERXSelectorUtilities; import er.extensions.foundation.ERXSystem; public interface ERXEnterpriseObject extends EOEnterpriseObject { /** logging support for modified objects */ public static final Logger logMod = Logger.getLogger("er.transaction.delegate.EREditingContextDelegate.modifiedObjects"); public static final boolean applyRestrictingQualifierOnInsert = ERXProperties.booleanForKey("er.extensions.ERXEnterpriseObject.applyRestrictingQualifierOnInsert"); /** * Registers as a listener for various editing context notifications and calls up the willXXX and * didXXX methods in objects that implement ERXEnterpriseObject. Subclass this and set the system * property <code>er.extensions.ERXEnterpriseObject.Observer.className</code> to set up your own subclass. * * @author ak */ public static class Observer { public void editingContextWillSaveChanges(NSNotification n) { EOEditingContext ec = (EOEditingContext) n.object(); boolean isNestedEditingContext = (ec.parentObjectStore() instanceof EOEditingContext); ec.processRecentChanges(); // need to do this to make sure the updated objects list is current if (ec.hasChanges()) { NSNotificationCenter.defaultCenter().postNotification(ERXExtensions.objectsWillChangeInEditingContext, ec); // we don't need to lock ec because we can assume that we're locked // before this method is called, but we do need to lock our parent if (isNestedEditingContext) { EOEditingContext parentEC = (EOEditingContext) ec.parentObjectStore(); parentEC.lock(); try { if (ec.deletedObjects().count() > 0) { final NSArray deletedObjectsToFlushInParent = ERXEOControlUtilities.localInstancesOfObjects(parentEC, ec.deletedObjects()); if (log.isDebugEnabled()) { log.debug("saveChanges: before save to child context " + ec + ", need to flush caches on deleted objects in parent context " + parentEC + ": " + deletedObjectsToFlushInParent); } ERXEnterpriseObject.FlushCachesProcessor.perform(parentEC, deletedObjectsToFlushInParent); } } finally { parentEC.unlock(); } } ERXEnterpriseObject.WillUpdateProcessor.perform(ec, ec.updatedObjects()); ERXEnterpriseObject.WillDeleteProcessor.perform(ec, ec.deletedObjects()); ERXEnterpriseObject.WillInsertProcessor.perform(ec, ec.insertedObjects()); if (log.isDebugEnabled()) log.debug("EditingContextWillSaveChanges: done calling will*"); if (logMod.isDebugEnabled()) { if (ec.updatedObjects()!=null) logMod.debug("** Updated Objects "+ ec.updatedObjects().count()+" - "+ ec.updatedObjects()); if (ec.insertedObjects()!=null) logMod.debug("** Inserted Objects "+ ec.insertedObjects().count()+" - "+ ec.insertedObjects()); if (ec.deletedObjects()!=null) logMod.debug("** Deleted Objects "+ ec.deletedObjects().count()+" - "+ ec.deletedObjects()); } } } public void editingContextDidSaveChanges(NSNotification n) { EOEditingContext ec = (EOEditingContext) n.object(); final boolean isNestedEditingContext = (ec.parentObjectStore() instanceof EOEditingContext); NSArray insertedObjects = (NSArray)n.userInfo().objectForKey("inserted"); NSArray updatedObjects = (NSArray)n.userInfo().objectForKey("updated"); NSArray deletedObjects = (NSArray)n.userInfo().objectForKey("deleted"); ERXEnterpriseObject.DidUpdateProcessor.perform(ec, updatedObjects); ERXEnterpriseObject.DidDeleteProcessor.perform(ec, deletedObjects); ERXEnterpriseObject.DidInsertProcessor.perform(ec, insertedObjects); if ( isNestedEditingContext ) { // we can assume insertedObjectGIDs and updatedObjectGIDs are non null. if we execute this branch, they're at // least empty arrays. final EOEditingContext parentEC = (EOEditingContext)ec.parentObjectStore(); if (insertedObjects.count() > 0 || updatedObjects.count() > 0) { NSMutableArray flushableObjects = new NSMutableArray(); flushableObjects.addObjectsFromArray(insertedObjects); flushableObjects.addObjectsFromArray(updatedObjects); parentEC.lock(); try { final NSArray flushableObjectsInParent = ERXEOControlUtilities.localInstancesOfObjects(parentEC, flushableObjects); if ( log.isDebugEnabled() ) { log.debug("saveChanges: before save to child context " + ec + ", need to flush caches on objects in parent context " + parentEC + ": " + flushableObjectsInParent); } ERXEnterpriseObject.FlushCachesProcessor.perform(parentEC, flushableObjectsInParent); } finally { parentEC.unlock(); } } } } private static volatile Observer observer; public static void install() { if(observer == null) { synchronized(Observer.class) { if(observer == null) { String name = ERXSystem.getProperty("er.extensions.ERXEnterpriseObject.Observer.className", Observer.class.getName()); Class c = ERXPatcher.classForName(name); try { observer = (Observer) c.newInstance(); NSNotificationCenter.defaultCenter().addObserver(observer, ERXSelectorUtilities.notificationSelector("editingContextWillSaveChanges"), ERXEC.EditingContextWillSaveChangesNotification, null); NSNotificationCenter.defaultCenter().addObserver(observer, ERXSelectorUtilities.notificationSelector("editingContextDidSaveChanges"), ERXEC.EditingContextDidSaveChangesNotification, null); } catch (InstantiationException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } catch (IllegalAccessException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } } } } } } public static abstract class Processor { protected abstract void perform(EOEditingContext ec, ERXEnterpriseObject eo); public void perform(EOEditingContext ec, NSArray eos) { if(eos != null && eos.count() > 0) { for (Enumeration enumerator = eos.objectEnumerator(); enumerator.hasMoreElements();) { EOEnterpriseObject eo = (EOEnterpriseObject) enumerator.nextElement(); if(eo instanceof ERXEnterpriseObject) { perform(ec, (ERXEnterpriseObject)eo); } } } } public void perform(EOEditingContext ec, EOEnterpriseObject eo) { if(eo instanceof ERXEnterpriseObject) { perform(ec, (ERXEnterpriseObject)eo); } } } public static Processor FlushCachesProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.flushCaches(); } }; public static Processor WillInsertProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.willInsert(); } }; public static Processor DidInsertProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.didInsert(); } }; public static Processor WillUpdateProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.willUpdate(); } }; public static Processor DidUpdateProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.didUpdate(); } }; public static Processor WillDeleteProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.willDelete(); } }; public static Processor DidDeleteProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.didDelete(ec); } }; public static Processor WillRevertProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.willRevert(); } }; public static Processor DidRevertProcessor = new Processor() { @Override protected void perform(EOEditingContext ec, ERXEnterpriseObject eo) { eo.didRevert(ec); } }; /** logging support. Called after an object is successfully inserted */ public static final Logger tranLogDidInsert = Logger .getLogger("er.transaction.eo.did.insert.ERXGenericRecord"); /** logging support. Called after an object is successfully deleted */ public static final Logger tranLogDidDelete = Logger .getLogger("er.transaction.eo.did.delete.ERXGenericRecord"); /** logging support. Called after an object is successfully updated */ public static final Logger tranLogDidUpdate = Logger .getLogger("er.transaction.eo.did.update.ERXGenericRecord"); /** logging support. Called after an object is reverted. **/ public static final Logger tranLogDidRevert = Logger .getLogger("er.transaction.eo.did.revert.ERXGenericRecord"); /** logging support. Called before an object is deleted */ public static final Logger tranLogMightDelete = Logger .getLogger("er.transaction.eo.might.delete.ERXGenericRecord"); /** logging support. Called before an object is inserted */ public static final Logger tranLogWillInsert = Logger .getLogger("er.transaction.eo.will.insert.ERXGenericRecord"); /** logging support. Called before an object is deleted */ public static final Logger tranLogWillDelete = Logger .getLogger("er.transaction.eo.will.delete.ERXGenericRecord"); /** logging support. Called before an object is updated */ public static final Logger tranLogWillUpdate = Logger .getLogger("er.transaction.eo.will.update.ERXGenericRecord"); /** logging support. Called before an object is reverted. **/ public static final Logger tranLogWillRevert = Logger .getLogger("er.transaction.eo.will.revert.ERXGenericRecord"); /** logging support for validation information */ public static final Logger validation = Logger .getLogger("er.eo.validation.ERXGenericRecord"); /** logging support for validation exceptions */ public static final Logger validationException = Logger .getLogger("er.eo.validationException.ERXGenericRecord"); /** logging support for insertion tracking */ public static final Logger insertionTrackingLog = Logger .getLogger("er.extensions.ERXGenericRecord.insertion"); /** general logging support */ public static final Logger log = Logger .getLogger("er.eo.ERXGenericRecord"); // DELETEME: Once we get rid of the half baked rule validation here, we can delete this. public final static String KEY_MARKER = "** KEY_MARKER **"; /** This methods checks if we already have created an Logger for this class * If not, one will be created, stored and returned on next request. * This method eliminates individual static variables for Logger's in all * subclasses. We use an NSDictionary here because static fields are class specific * and thus something like lazy initialization would not work in this case. * * @return an {@link Logger} for this objects class */ public abstract Logger getClassLog(); /** * self is useful for D2W purposes * * @return the EO itself */ public abstract ERXEnterpriseObject self(); /** * Called as part of the augmented transaction process. * This method is called when deleteObject() is called on * the editing context. The benefit over willDelete() is that in this * method, the relationships are still intact. Mostly, at least, * as it's also called when the deletes cascade. */ public abstract void mightDelete(); /** * Called as part of the augmented transaction process. * This method is called after saveChanges is called on * the editing context, but before the object is actually * deleted from the database. This method is also called * before <code>validateForDelete</code> is called on this * object. This method is called by the editing context * delegate {@link ERXDefaultEditingContextDelegate}. * @throws NSValidation.ValidationException to stop the object * from being deleted. */ public abstract void willDelete() throws NSValidation.ValidationException; /** * Called as part of the augmented transaction process. * This method is called after saveChanges is called on * the editing context, but before the object is actually * inserted into the database. This method is also called * before <code>validateForInsert</code> is called on this * object. This method is called by the editing context * delegate {@link ERXDefaultEditingContextDelegate}. */ public abstract void willInsert(); /** * Called as part of the augmented transaction process. * This method is called after saveChanges is called on * the editing context, but before the object is actually * updated in the database. This method is also called * before <code>validateForSave</code> is called on this * object. This method is called by the editing context * delegate {@link ERXDefaultEditingContextDelegate}. */ public abstract void willUpdate(); /** * This is called when an object has had * changes merged into it by the editing context. * This is called by {@link ERXDefaultEditingContextDelegate} * after it merges changes. Any caches that an object * keeps based on any of it's values it should flush. * The default implementation of this method does * nothing. */ public abstract void flushCaches(); /** * Called on the object after is has been deleted. * The editing context is passed to the object since * by this point the editingContext of the object is * null. You should check if the <code>ec</code> * is a child context when doing something here that * can't be undone. * @param ec editing context that used to be associated * with the object. */ public abstract void didDelete(EOEditingContext ec); /** * Called on the object after is has successfully * been updated in the database. */ public abstract void didUpdate(); /** * Called on the object after is has successfully * been inserted into the database. */ public abstract void didInsert(); /** * Called on the object before it will be reverted. * * Default implementation does nothing other than log. */ public abstract void willRevert(); /** * Called on the object after it has been reverted. * The editing context is passed to the object because * if the object was in the insertedObjects list before * the revert, the object has had its editingContext * nulled. * * Default implementation calls <code>flushCaches</code>. * * @param ec editing context that is either currently associated * with the object if the object was marked as changed or deleted before * the revert, otherwise the editing context that was associated with the object * before the revert. */ public abstract void didRevert(EOEditingContext ec); /** * Adds a collection of objects to a given relationship by calling * <code>addObjectToBothSidesOfRelationshipWithKey</code> for all * objects in the collection. * @param objects objects to add to both sides of the given relationship * @param key relationship key */ public abstract void addObjectsToBothSidesOfRelationshipWithKey( NSArray<? extends EOEnterpriseObject> objects, String key); /** * Removes a collection of objects to a given relationship by calling * <code>removeObjectFromBothSidesOfRelationshipWithKey</code> for all * objects in the collection. * @param objects objects to be removed from both sides of the given relationship * @param key relationship key */ public abstract void removeObjectsFromBothSidesOfRelationshipWithKey( NSArray<? extends EOEnterpriseObject> objects, String key); /** * Removes a collection of objects to a given relationship by calling * <code>removeObjectFromPropertyWithKey</code> for all * objects in the collection. * @param objects objects to be removed from both sides of the given relationship * @param key relationship key */ public abstract void removeObjectsFromPropertyWithKey(NSArray<? extends EOEnterpriseObject> objects, String key); /** * Primary key of the object as a String. * @return primary key for the given object as a String */ public abstract String primaryKey(); /** * Calling this method will return the primary key of the * given enterprise object or if one has not been assigned * to it yet, then it will have the adaptor channel generate * one for it, cache it and then use that primary key when it * is saved to the database. If you just want the * primary key of the object or null if it doesn't have one * yet, use the method <code>rawPrimaryKey</code>. * @return the primary key of this object. */ public abstract Object rawPrimaryKeyInTransaction(); /** * Calling this method will return the primary key of the * given enterprise object or if one has not been assigned * to it yet, then it will have the adaptor channel generate * one for it, cache it and then use that primary key when it * is saved to the database. This method returns the string * representation of the primary key. If you just want the * primary key of the object or null if it doesn't have one * yet, use the method <code>primaryKey</code>. * @return string representation of the primary key of this * object. */ public abstract String primaryKeyInTransaction(); /** * Gives the raw primary key of the object. This could be anything from * an NSData to a BigDecimal. * @return the raw primary key of this object. */ public abstract Object rawPrimaryKey(); /** * Takes the primary key of the object and encrypts it * with the blowfish cipher using {@link er.extensions.crypting.ERXCrypto ERXCrypto}. * @return blowfish encrypted primary key */ public abstract String encryptedPrimaryKey(); /** * Returns the foreign key for a given relationship. * @param rel relationship key * @return foreign key for a given relationship. */ public abstract Object foreignKeyForRelationshipWithKey(String rel); /** * Returns the names of all primary key attributes. * * @return list of attribute names */ public abstract NSArray<String> primaryKeyAttributeNames(); /** * Determines what the value of the given key is in the committed * snapshot * @param key to be checked in committed snapshot * @return the committed snapshot value for the given key */ public abstract Object committedSnapshotValueForKey(String key); /** * Returns an EO in the same editing context as the caller. * * @param eo to local instance * @return an EO in the same editing context as the caller. */ public abstract <T extends EOEnterpriseObject> T localInstanceOf(T eo); /** * Returns this EO in the supplied editing context. * * @param ec editing context to local instance in * @return this EO in the supplied editing context. */ public EOEnterpriseObject localInstanceIn(EOEditingContext ec); /** * Returns an array of EOs in the same editing context as the caller. * * @param eos array of EOs to local instance * @return array of EOs in the same editing context as the caller. */ public abstract <T extends EOEnterpriseObject> NSArray<T> localInstancesOf(NSArray<T> eos); /** * Computes the current set of changes that this object has from the * currently committed snapshot. * @return a dictionary holding the changed values from the currently * committed snapshot. */ public abstract NSDictionary<String, Object> changesFromCommittedSnapshot(); /** * Simple method that will return if the parent object store of this object's editing * context is an instance of {@link com.webobjects.eocontrol.EOObjectStoreCoordinator EOObjectStoreCoordinator}. The reason this is important * is because if this condition evaluates to true then when changes are saved in this * editing context they will be propagated to the database. * @return if the parent object store of this object's editing context is an EOObjectStoreCoordinator. */ public abstract boolean parentObjectStoreIsObjectStoreCoordinator(); /** * Method that will make sure to fetch an eo from the Database and * place it in the editingContext provided * as an argument * @param ec the editing context in which the result will be placed * @return fresh instance of an EO fetched from the DB and placed in the editing context argument */ public abstract ERXEnterpriseObject refetchObjectFromDBinEditingContext( EOEditingContext ec); /** * Returns the super classes implementation of toString * which prints out the current key-value pairs for all * of the attributes and relationships for the current * object. Very verbose. * @return super's implementation of <code>toString</code>. */ public abstract String toLongString(); /** * This method will trim the leading and trailing white * space from any attributes that are mapped to a String * object. This method is called before the object is saved * to the database. Override this method to do nothing if * you wish to preserve your leading and trailing white space. */ public abstract void trimSpaces(); /** * Determines if this object is a deleted object by * checking to see if it is included in the deletedObjects * array of the editing context or - if it's editing context * is null - it already has a global id. * @return if the object is a deleted object */ public abstract boolean isDeletedEO(); /** * Determines if this object is a new object and * hasn't been saved to the database yet. This * method just calls the method ERExtensions.isNewObject * passing in this object as the current parameter. Note * that an object that has been successfully deleted will * also look as if it is a new object because it will have * a null editing context. * @return if the object is a new enterprise object. */ public abstract boolean isNewObject(); /** * Toggles whether or not inverse relationships should be updates. This is * called by ERXGenericRecord.InverseRelationshipUpdater to prevent infinite * loops and should not be called by anything else unless you know exactly * what you are doing. * * @param newValue whether or not inverse relationships should be updated * @return the previous setting of the updateInverseRelationships setting */ public abstract boolean _setUpdateInverseRelationships(boolean newValue); }