package er.extensions.eof; import java.lang.reflect.InvocationTargetException; import com.webobjects.eoaccess.EOAttribute; import com.webobjects.eoaccess.EODatabase; import com.webobjects.eoaccess.EODatabaseContext; import com.webobjects.eoaccess.EODatabaseOperation; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.ERXEOAccessHelper; 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.NSKeyValueCoding; import com.webobjects.foundation.NSLog; public class ERXDatabaseContext extends EODatabaseContext { private static ThreadLocal _fetching = new ThreadLocal(); protected static Class<? extends ERXDatabase> _dbClass = null; public ERXDatabaseContext( EODatabase database ) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { super( _dbClass != null ? _dbClass.getConstructor( EODatabase.class ).newInstance( database ) : new ERXDatabase( database ) ); } public static void setDatabaseContextClass( Class<? extends ERXDatabase> cls ) { NSLog.out.appendln( "Setting ERXDatabase subclass to " + cls.getName() ); _dbClass = cls; } public static boolean isFetching() { Boolean fetching = (Boolean) _fetching.get(); // System.out.println("ERXDatabaseContext.isFetching: " + // Thread.currentThread() + ", " + fetching); return fetching != null && fetching.booleanValue(); } public static void setFetching(boolean fetching) { // System.out.println("ERXDatabaseContext.setFetching: " + // Thread.currentThread() + ", " + fetching); _fetching.set(Boolean.valueOf(fetching)); } @Override public NSArray objectsForSourceGlobalID(EOGlobalID gid, String name, EOEditingContext context) { NSArray results; boolean fetching = isFetching(); if (!fetching) { setFetching(true); } try { results = super.objectsForSourceGlobalID(gid, name, context); } finally { if (!fetching) { setFetching(false); } } return results; } @Override public NSArray _objectsWithFetchSpecificationEditingContext(EOFetchSpecification fetchSpec, EOEditingContext context) { NSArray results; boolean fetching = isFetching(); if (!fetching) { setFetching(!fetchSpec.refreshesRefetchedObjects()); } try { results = super._objectsWithFetchSpecificationEditingContext(fetchSpec, context); } finally { if (!fetching) { setFetching(false); } } return results; } @Override public void _followFetchSpecification(EOFetchSpecification fetchSpec, String relationshipName, NSArray sourceObjects, EOEditingContext context) { fetchSpec = ERXEOAccessHelper.adjustPrefetching(this, fetchSpec, relationshipName, sourceObjects, context); super._followFetchSpecification(fetchSpec, relationshipName, sourceObjects, context); } @Override public void _verifyNoChangesToReadonlyEntity(EODatabaseOperation dbOp) { EOEntity entity = dbOp.entity(); if (entity.isReadOnly()) { switch (dbOp.databaseOperator()) { case 0: // '\0' return; case 1: // '\001' throw new IllegalStateException("cannot insert object:" + dbOp.object() + " that corresponds to read-only entity: " + entity.name() + " in databaseContext " + this); case 3: // '\003' throw new IllegalStateException("cannot delete object:" + dbOp.object() + " that corresponds to read-only entity:" + entity.name() + " in databaseContext " + this); case 2: // '\002' if (!dbOp.dbSnapshot().equals(dbOp.newRow())) { throw new IllegalStateException("cannot update '" + dbOp.rowDiffsForAttributes(entity.attributes()).allKeys() + "' keys on object:" + dbOp.object() + " that corresponds to read-only entity: " + entity.name() + " in databaseContext " + this); } return; } } // HACK: ak these methods are protected, so we call them via KVC if (dbOp.databaseOperator() == 2 && ((Boolean) NSKeyValueCoding.Utility.valueForKey(entity, "_hasNonUpdateableAttributes")).booleanValue()) { NSArray keys = (NSArray) NSKeyValueCoding.Utility.valueForKey(entity, "dbSnapshotKeys"); NSDictionary dbSnapshot = dbOp.dbSnapshot(); NSDictionary newRow = dbOp.newRow(); for (int i = keys.count() - 1; i >= 0; i--) { String key = (String) keys.objectAtIndex(i); EOAttribute att = entity.attributeNamed(key); // FIX: ak when you have single-table inheritance and in the // child there are foreign keys that are not in the parent // THEN, if the entity _hasNonUpdateableAttributes (public PK or // read only props) the DB op is checked // against the attributes. BUT this dictionary has all entries, // even from the child (most likely NULL values) // and the current implementation doesn't check against the case // when the attribute isn't present in the first place. // SO we add this check and live happily ever after if (att != null && att._isNonUpdateable() && !dbSnapshot.objectForKey(key).equals(newRow.objectForKey(key))) { if (att.isReadOnly()) { throw new IllegalStateException("cannot update read-only key '" + key + "' on object:" + dbOp.object() + " of entity: " + entity.name() + " in databaseContext " + this); } throw new IllegalStateException("cannot update primary-key '" + key + "' from '" + dbSnapshot.objectForKey(key) + "' to '" + newRow.objectForKey(key) + "' on object:" + dbOp.object() + " of entity: " + entity.name() + " in databaseContext " + this); } } } } }