package er.extensions.eof; import java.lang.reflect.Method; import com.webobjects.eoaccess.EOAdaptorChannel; import com.webobjects.eoaccess.EODatabase; import com.webobjects.eoaccess.EODatabaseChannel; import com.webobjects.eoaccess.EODatabaseContext; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOFetchSpecification; import com.webobjects.eocontrol.EOGlobalID; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSForwardException; import er.extensions.foundation.ERXMulticastingDelegate; /** * Subclass of <code>er.extensions.foundation.ERXMulticastingDelegate</code> that implements * <code>com.webobjects.eoaccess.EODatabaseContext.Delegate</code>. Use this to aggregate multiple delegate objects * for <code>EODatabaseContext.Delegate</code> * * @see er.extensions.foundation.ERXMulticastingDelegate * @see com.webobjects.eoaccess.EODatabaseContext.Delegate * @author chill */ public class ERXDatabaseContextMulticastingDelegate extends ERXMulticastingDelegate { private Method orderAdaptorOperations; private Method currentEditingContext; public ERXDatabaseContextMulticastingDelegate() { super(); try { orderAdaptorOperations = EODatabaseContext.class.getDeclaredMethod("orderAdaptorOperations", new Class[] {}); orderAdaptorOperations.setAccessible(true); currentEditingContext = EODatabaseChannel.class.getDeclaredMethod("currentEditingContext", new Class[] {}); currentEditingContext.setAccessible(true); } catch (Exception e) { throw NSForwardException._runtimeExceptionForThrowable(e); } } /** * <p>Convenience method to add <code>newDelegate</code> as the last delegate called for * <code>EODatabaseContext.defaultDelegate()</code>. There are three cases to handle:</p> * <ol> * <li>If there is no default delegate defined, an <code>ERXDatabaseContextMulticastingDelegate</code> * is created as the default delegate, and <code>newDelegate</code> added.</li> * * <li>If there is a default delegate defined, and it is a <code>ERXDatabaseContextMulticastingDelegate</code>, * <code>newDelegate</code> is added at the end of the delegate chain.</li> * * <li>If there is a default delegate defined, and it is not a <code>ERXDatabaseContextMulticastingDelegate</code>, * an <code>ERXDatabaseContextMulticastingDelegate</code> is created as the default delegate, the existing delegate is * added, then <code>newDelegate</code> is added at the end of the delegate chain.</li> * </ol> * * @param newDelegate object to include as delegate */ public static void addDefaultDelegate(Object newDelegate) { ERXDatabaseContextMulticastingDelegate multiDelegate; if (EODatabaseContext.defaultDelegate() == null) { multiDelegate = new ERXDatabaseContextMulticastingDelegate(); } else { if (EODatabaseContext.defaultDelegate() instanceof ERXDatabaseContextMulticastingDelegate) { multiDelegate = (ERXDatabaseContextMulticastingDelegate)EODatabaseContext.defaultDelegate(); } else { multiDelegate = new ERXDatabaseContextMulticastingDelegate(); multiDelegate.addDelegate(EODatabaseContext.defaultDelegate()); } } multiDelegate.addDelegate(newDelegate); EODatabaseContext.setDefaultDelegate(multiDelegate); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextDidFetchObjects(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.foundation.NSArray, com.webobjects.eocontrol.EOFetchSpecification, com.webobjects.eocontrol.EOEditingContext) */ public void databaseContextDidFetchObjects(EODatabaseContext dbCtxt, NSArray array, EOFetchSpecification fetchSpec, EOEditingContext ec) { perform("databaseContextDidFetchObjects", new Object[] {dbCtxt, array, fetchSpec, ec}, null); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextDidSelectObjects(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eocontrol.EOFetchSpecification, com.webobjects.eoaccess.EODatabaseChannel) */ public void databaseContextDidSelectObjects(EODatabaseContext dbCtxt, EOFetchSpecification fetchSpec, EODatabaseChannel dbChannel) { perform("databaseContextDidSelectObjects", new Object[] {dbCtxt, fetchSpec, dbChannel}, null); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextFailedToFetchObject(com.webobjects.eoaccess.EODatabaseContext, java.lang.Object, com.webobjects.eocontrol.EOGlobalID) */ public boolean databaseContextFailedToFetchObject(EODatabaseContext dbCtxt, Object object, EOGlobalID gid) { return booleanPerform("databaseContextFailedToFetchObject", new Object[] {dbCtxt, object, gid}, false); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextNewPrimaryKey(com.webobjects.eoaccess.EODatabaseContext, java.lang.Object, com.webobjects.eoaccess.EOEntity) */ public NSDictionary databaseContextNewPrimaryKey(EODatabaseContext dbCtxt, Object object, EOEntity entity) { return (NSDictionary)perform("databaseContextNewPrimaryKey", new Object[] {dbCtxt, object, entity}, null); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldFetchArrayFault(com.webobjects.eoaccess.EODatabaseContext, java.lang.Object) */ public boolean databaseContextShouldFetchArrayFault(EODatabaseContext dbCtxt, Object object) { return booleanPerform("databaseContextShouldFetchArrayFault", new Object[] {dbCtxt, object}, true); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldFetchObjectFault(com.webobjects.eoaccess.EODatabaseContext, java.lang.Object) */ public boolean databaseContextShouldFetchObjectFault(EODatabaseContext dbCtxt, Object object) { return booleanPerform("databaseContextShouldFetchObjectFault", new Object[] {dbCtxt, object}, true); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldFetchObjects(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eocontrol.EOFetchSpecification, com.webobjects.eocontrol.EOEditingContext) */ public NSArray databaseContextShouldFetchObjects(EODatabaseContext dbCtxt, EOFetchSpecification fetchSpec, EOEditingContext ec) { return (NSArray)perform("databaseContextShouldFetchObjects", new Object[] {dbCtxt, fetchSpec, ec}, null); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldHandleDatabaseException(com.webobjects.eoaccess.EODatabaseContext, java.lang.Throwable) */ public boolean databaseContextShouldHandleDatabaseException(EODatabaseContext dbCtxt, Throwable exception) { return booleanPerform("databaseContextShouldHandleDatabaseException", new Object[] {dbCtxt, exception}, true); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldInvalidateObjectWithGlobalID(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eocontrol.EOGlobalID, com.webobjects.foundation.NSDictionary) */ public boolean databaseContextShouldInvalidateObjectWithGlobalID(EODatabaseContext dbCtxt, EOGlobalID gid, NSDictionary dic) { return booleanPerform("databaseContextShouldInvalidateObjectWithGlobalID", new Object[] {dbCtxt, gid, dic}, true); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldLockObjectWithGlobalID(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eocontrol.EOGlobalID, com.webobjects.foundation.NSDictionary) */ public boolean databaseContextShouldLockObjectWithGlobalID(EODatabaseContext dbCtxt, EOGlobalID gid, NSDictionary dic) { return booleanPerform("databaseContextShouldLockObjectWithGlobalID", new Object[] {dbCtxt, gid, dic}, true); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldRaiseExceptionForLockFailure(com.webobjects.eoaccess.EODatabaseContext, java.lang.Throwable) */ public boolean databaseContextShouldRaiseExceptionForLockFailure(EODatabaseContext dbCtxt, Throwable exception) { return booleanPerform("databaseContextShouldRaiseExceptionForLockFailure", new Object[] {dbCtxt, exception}, true); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldSelectObjects(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eocontrol.EOFetchSpecification, com.webobjects.eoaccess.EODatabaseChannel) */ public boolean databaseContextShouldSelectObjects(EODatabaseContext dbCtxt, EOFetchSpecification fetchSpec, EODatabaseChannel dbChannel) { return booleanPerform("databaseContextShouldSelectObjects", new Object[] {dbCtxt, fetchSpec, dbChannel}, true); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldUpdateCurrentSnapshot(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.foundation.NSDictionary, com.webobjects.foundation.NSDictionary, com.webobjects.eocontrol.EOGlobalID, com.webobjects.eoaccess.EODatabaseChannel) * @see EODatabase#snapshotForGlobalID(EOGlobalID, long) */ public NSDictionary databaseContextShouldUpdateCurrentSnapshot(EODatabaseContext dbCtxt, NSDictionary existingSnapshot, NSDictionary fetchedRow, EOGlobalID gid, EODatabaseChannel dbChannel) { NSDictionary resultSnapshot = (NSDictionary)perform("databaseContextShouldUpdateCurrentSnapshot", dbCtxt, existingSnapshot, fetchedRow, gid, dbChannel, null); if (resultSnapshot != null) { return resultSnapshot; } /* There is no way for this delegate method to say "do what you normally do". Our two choices for a default return value are never update the snapshot or always update it. What we want is neither, we want the unchanged EOF behavior. So we re-implement what EODatabaseChannel would so in the absence of this delegate method: updating of the snapshot depends whether we are refreshing and on the snapshot's age and the fetchTimestamp of the EC doing the fetching. */ try { // Again with the object rape. EODatabaseChannel.currentEditingContext() is private and the underlying instance variable // is protected. We need the ec, so we do what we have to... EOEditingContext ec = (EOEditingContext) currentEditingContext.invoke(dbChannel, new Object[] {}); // Get the snapshot if it has not expired. cachedSnapshot will be null if it has expired // If not null, it should be the same as the existingSnapshot parameter NSDictionary cachedSnapshot = dbCtxt.database().snapshotForGlobalID(gid, ec.fetchTimestamp()); // If we are refreshing or the snapshot in the cache has timed out, but the fetched row // matches the cached snapshot, reset the time stamp by recording the existing snapshot again. if (existingSnapshot.equals(fetchedRow) && (dbChannel.isRefreshingObjects() || cachedSnapshot == null)) { dbCtxt.database().recordSnapshotForGlobalID(existingSnapshot, gid); } // Handle refreshing fetches. If the fetched data is the same, we return // existingSnapshot to avoid a bug in EODatabaseChannel where == is used // instead of equals() when this delegate method is called. Without this, // EOObjectStore.ObjectsChangedInStoreNotification will be sent when there are no real changes. // This should be fixed in 5.4.3 but is left here for 5.3 compatibility if (dbChannel.isRefreshingObjects()) { return existingSnapshot.equals(fetchedRow) ? existingSnapshot : fetchedRow; } // Same as for refreshing, if the snapshot has expired but is unchanged in the database, // return the existing snapshot if (cachedSnapshot == null && existingSnapshot.equals(fetchedRow)) { return existingSnapshot; } // Return the existing snapshot unless it has expired return cachedSnapshot != null ? existingSnapshot : fetchedRow; } catch (Exception e) { throw NSForwardException._runtimeExceptionForThrowable(e); } } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextShouldUsePessimisticLock(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eocontrol.EOFetchSpecification, com.webobjects.eoaccess.EODatabaseChannel) */ public boolean databaseContextShouldUsePessimisticLock(EODatabaseContext dbCtxt, EOFetchSpecification fetchSpec, EODatabaseChannel dbChannel) { return booleanPerform("databaseContextShouldUsePessimisticLock", new Object[] {dbCtxt, fetchSpec, dbChannel}, false); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextWillFireArrayFaultForGlobalID(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eocontrol.EOGlobalID, com.webobjects.eoaccess.EORelationship, com.webobjects.eocontrol.EOFetchSpecification, com.webobjects.eocontrol.EOEditingContext) */ public void databaseContextWillFireArrayFaultForGlobalID(EODatabaseContext dbCtxt, EOGlobalID gid, EORelationship rel, EOFetchSpecification fetchSpec, EOEditingContext ec) { perform("databaseContextWillFireArrayFaultForGlobalID", new Object[] {dbCtxt, gid, rel, fetchSpec, ec}, null); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextWillFireObjectFaultForGlobalID(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eocontrol.EOGlobalID, com.webobjects.eocontrol.EOFetchSpecification, com.webobjects.eocontrol.EOEditingContext) */ public void databaseContextWillFireObjectFaultForGlobalID(EODatabaseContext dbCtxt, EOGlobalID gid, EOFetchSpecification fetchSpec, EOEditingContext ec) { perform("databaseContextWillFireObjectFaultForGlobalID", new Object[] {dbCtxt, gid, fetchSpec, ec}, null); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextWillOrderAdaptorOperations(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.foundation.NSArray) */ public NSArray databaseContextWillOrderAdaptorOperations(EODatabaseContext dbCtxt, NSArray databaseOps) { NSArray result = (NSArray)perform("databaseContextWillOrderAdaptorOperations", new Object[] {dbCtxt, databaseOps}, null); /* OK, this is really quite brutal. This delegate method has no way of returning an "I did not handle this so do the default thing" * response. The default thing is private so it can't be called directly. Rather than re-implement it, we use reflection to * gain access to the private default implementation private NSArray orderAdaptorOperations(). */ if (result == null) { try { result = (NSArray) orderAdaptorOperations.invoke(dbCtxt, new Object[] {}); } catch (Exception e) { throw NSForwardException._runtimeExceptionForThrowable(e); } } return result; } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextWillPerformAdaptorOperations(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.foundation.NSArray, com.webobjects.eoaccess.EOAdaptorChannel) */ public NSArray databaseContextWillPerformAdaptorOperations(EODatabaseContext dbCtxt, NSArray adaptorOps, EOAdaptorChannel adChannel) { return (NSArray)perform("databaseContextWillPerformAdaptorOperations", new Object[] {dbCtxt, adaptorOps, adChannel}, adaptorOps); } /** * @see com.webobjects.eoaccess.EODatabaseContext.Delegate#databaseContextWillRunLoginPanelToOpenDatabaseChannel(com.webobjects.eoaccess.EODatabaseContext, com.webobjects.eoaccess.EODatabaseChannel) */ public boolean databaseContextWillRunLoginPanelToOpenDatabaseChannel(EODatabaseContext dbCtxt, EODatabaseChannel dbChannel) { return booleanPerform("databaseContextWillRunLoginPanelToOpenDatabaseChannel", new Object[] {dbCtxt, dbChannel}, true); } }