From: Ricardo J. P. <rp...@me...> - 2009-09-17 23:24:04
|
Is this documented anywhere? Wonder blog soon maybe so I can check it out? :-p Sent from my iPhone On Sep 17, 2009, at 7:16 PM, Lachlan Deck <lac...@gm...> wrote: > Very cool, Anjo. I'd almost be inclined to turn this on by default in > ERExtensions so that toMany relations aren't so dangerous to follow > anymore out of the box. > > On 17/09/2009, at 8:38 PM, an...@us... wrote: > >> Revision: 9874 >> http://wonder.svn.sourceforge.net/wonder/?rev=9874&view=rev >> Author: anjo >> Date: 2009-09-17 10:38:44 +0000 (Thu, 17 Sep 2009) >> >> Log Message: >> ----------- >> automatic batch faulting support >> >> Modified Paths: >> -------------- >> trunk/Wonder/Frameworks/Core/ERExtensions/Resources/Properties >> trunk/Wonder/Frameworks/Core/ERExtensions/Sources/er/extensions/ >> eof/ERXDatabaseContextDelegate.java >> trunk/Wonder/Frameworks/Core/ERExtensions/Sources/er/extensions/ >> eof/ERXGenericRecord.java >> >> Modified: trunk/Wonder/Frameworks/Core/ERExtensions/Resources/ >> Properties >> =================================================================== >> --- trunk/Wonder/Frameworks/Core/ERExtensions/Resources/Properties >> 2009-09-17 05:06:20 UTC (rev 9873) >> +++ trunk/Wonder/Frameworks/Core/ERExtensions/Resources/Properties >> 2009-09-17 10:38:44 UTC (rev 9874) >> @@ -286,6 +286,9 @@ >> ## only log an error. Default is unset. >> # er.extensions.ERXDatabaseContextDelegate.tolerantEntityPattern = ^ >> (Entity1|Entity3)$ >> >> +## The batch size for automatic batch faulting, default is 0 for >> unset >> +# er.extensions.ERXDatabaseContextDelegate.autoBatchFetchSize = 100 >> + >> ### >> ### >> ################################################################### >> # ERXEC >> ### >> ### >> ################################################################### >> >> Modified: trunk/Wonder/Frameworks/Core/ERExtensions/Sources/er/ >> extensions/eof/ERXDatabaseContextDelegate.java >> =================================================================== >> --- trunk/Wonder/Frameworks/Core/ERExtensions/Sources/er/extensions/ >> eof/ERXDatabaseContextDelegate.java 2009-09-17 05:06:20 UTC (rev >> 9873) >> +++ trunk/Wonder/Frameworks/Core/ERExtensions/Sources/er/extensions/ >> eof/ERXDatabaseContextDelegate.java 2009-09-17 10:38:44 UTC (rev >> 9874) >> @@ -33,8 +33,10 @@ >> import com.webobjects.eocontrol.EOGlobalID; >> import com.webobjects.eocontrol.EOKeyGlobalID; >> import com.webobjects.eocontrol.EOSharedEditingContext; >> +import >> com.webobjects.eocontrol.EOGenericRecord._LazyDictionaryBinding; >> import com.webobjects.foundation.NSArray; >> import com.webobjects.foundation.NSDictionary; >> +import com.webobjects.foundation.NSKeyValueCoding; >> import com.webobjects.foundation.NSMutableArray; >> import com.webobjects.foundation.NSMutableSet; >> import com.webobjects.foundation.NSNotificationCenter; >> @@ -133,18 +135,22 @@ >> } >> >> /** >> - * Sets the cache entry for the fetched objects. >> + * Sets the cache entry for the fetched objects and refreshes >> the >> timestamps >> + * for fetched objects if batch faulting is enabled. >> + * >> * @param dbc >> * @param eos >> * @param fs >> * @param ec >> */ >> public void databaseContextDidFetchObjects(EODatabaseContext dbc, >> NSArray eos, EOFetchSpecification fs, EOEditingContext ec) { >> - NSArray result = null; >> ERXFetchResultCache fetchResultCache = fetchResultCache(); >> if (fetchResultCache != null) { >> fetchResultCache.setObjectsForFetchSpecification(dbc, >> ec, eos, fs); >> } >> + if(autoBatchFetchSize() > 0) { >> + freshenFetchTimestamps(eos, ec); >> + } >> } >> >> /** >> @@ -321,18 +327,7 @@ >> dbLog.debug("databaseContextDidSelectObjects " + fs, new >> Exception()); >> } >> } >> - >> - private boolean fetching = false; >> >> - /** >> - * Simple interface to provide batch fetching of to-many faults. >> - * @author ak >> - * >> - */ >> - public interface FetchTimestampedEO { >> - public long __fetchTimestamp(); >> - } >> - >> /** >> * This delegate method first checks the arrayFaultCache if it is >> set before >> * trying to resolve the fault from the DB. It can be a severe >> performance >> @@ -350,43 +345,26 @@ >> return false; >> } >> } >> - //AK: experimental code to have auto-batch faulting... >> - EOAccessArrayFaultHandler handler = >> (EOAccessArrayFaultHandler) EOFaultHandler.handlerForFault(obj); >> - if(handler != null && !fetching && false) { >> - fetching = true; >> - try { >> - EOGlobalID source = handler.sourceGlobalID(); >> - ERXEC ec = (ERXEC)handler.editingContext(); >> - String key = handler.relationshipName(); >> - EOEnterpriseObject sourceEO = ec.faultForGlobalID >> (source, ec); >> - EOEntityClassDescription cd = >> (EOEntityClassDescription) >> ec.objectForGlobalID(source).classDescription(); >> - EORelationship relationship = cd.entity >> ().relationshipNamed(key); >> - if(sourceEO instanceof FetchTimestampedEO && ! >> (relationship.destinationEntity().isAbstractEntity())) { >> - long timestamp = ((FetchTimestampedEO) >> sourceEO).__fetchTimestamp(); >> - NSMutableArray<EOEnterpriseObject> eos = new >> NSMutableArray<EOEnterpriseObject>(); >> - for(EOEnterpriseObject eo: >> (NSArray<EOEnterpriseObject>) >> ec.registeredObjects()) { >> - if (eo instanceof FetchTimestampedEO) { >> - if(((FetchTimestampedEO) >> eo).__fetchTimestamp() == >> timestamp) { >> - if(!EOFaultHandler.isFault(eo) && >> eo.classDescription() == >> sourceEO.classDescription() && EOFaultHandler.isFault >> (eo.storedValueForKey(key))) { >> - eos.addObject(eo); >> - } >> - } >> - } >> - } >> - if(eos.count() > 1) { >> - dbc.batchFetchRelationship(relationship, >> eos, ec); >> - log.info("Fetched " + eos.count() + " for >> " + key); >> - return EOFaultHandler.isFault(obj); >> - } >> - } >> - } finally { >> - fetching = false; >> - } >> + if(autoBatchFetchSize() > 0) { >> + return batchFetchToManyFault(dbc, obj); >> } >> return true; >> } >> >> /** >> + * Batch fetches to one relationships if enabled. >> + * @param dbc >> + * @param obj >> + * @return true if the fault should get fetched >> + */ >> + public boolean databaseContextShouldFetchObjectFault >> (EODatabaseContext dbc, Object obj) { >> + if(autoBatchFetchSize() > 0 && obj instanceof >> AutoBatchFaultingEnterpriseObject) { >> + return batchFetchToOneFault(dbc, >> (AutoBatchFaultingEnterpriseObject)obj); >> + } >> + return true; >> + } >> + >> + /** >> * Overridden to remove inserts and deletes of the "same" row. >> When you >> * delete from a join table and then re-add the same object, >> then the >> * order of operations would be insert, then delete and you will >> get >> @@ -446,4 +424,206 @@ >> } >> return result; >> } >> + >> + /** >> + * The delegate is not reentrant, so this marks whether we are >> already batch faulting a to-many relationship. >> + */ >> + private boolean fetchingToMany = false; >> + >> + /** >> + * The delegate is not reentrant, so this marks whether we are >> already batch faulting a to-one relationship. >> + */ >> + private boolean fetchingToOne = false; >> + >> + /** >> + * Holds the auto batch fetch size. >> + */ >> + private static int autoBatchFetchSize = -1; >> + >> + /** >> + * Returns the batch size for automatic batch faulting from the >> System property >> <code>er.extensions.ERXDatabaseContextDelegate.autoBatchFetchSize</ >> code>. >> + * Default is 0, meaning it's disabled. >> + * @return batch size >> + */ >> + public static int autoBatchFetchSize() { >> + if(autoBatchFetchSize == -1) { >> + autoBatchFetchSize = ERXProperties.intForKeyWithDefault >> ("er.extensions.ERXDatabaseContextDelegate.autoBatchFetchSize", 0); >> + } >> + return autoBatchFetchSize; >> + } >> + >> + /** >> + * Interface to provide auto-magic batch fetching. For an >> implementation (and the hack needed for to-one handling), see {@link >> ERXGenericRecord}. >> + */ >> + >> + public interface AutoBatchFaultingEnterpriseObject extends >> EOEnterpriseObject { >> + /** >> + * Last time fetched. >> + * @return millis of last fetch. >> + */ >> + public long batchFaultingTimeStamp(); >> + >> + /** >> + * Updates the last time this object was fetched. >> + * @param fetchTimestamp >> + */ >> + public void setBatchFaultingTimestamp(long fetchTimestamp); >> + >> + /** >> + * Marks the object as touched from the specified source >> with >> the specified key. >> + * @param toucher >> + * @param relationship name of relationship >> + */ >> + public void touchFromBatchFaultingSource >> (AutoBatchFaultingEnterpriseObject toucher, String relationship); >> + >> + /** >> + * The GID of the object that last touched us, or null. >> + * @return gid of touching object. >> + */ >> + public EOGlobalID batchFaultingSourceGlobalID(); >> + >> + /** >> + * The key through which we were last accessed. >> + * @return relationship name or null >> + */ >> + public String batchFaultingRelationshipName(); >> + } >> + >> + /** >> + * Refreshes the fetch timestamp for the fetched objects. >> + * @param eos >> + * @param ec >> + */ >> + private void freshenFetchTimestamps(NSArray eos, >> EOEditingContext >> ec) { >> + for(Object eo: eos) { >> + if (eo instanceof AutoBatchFaultingEnterpriseObject) { >> + AutoBatchFaultingEnterpriseObject ft = >> (AutoBatchFaultingEnterpriseObject) eo; >> + ft.setBatchFaultingTimestamp(ec.fetchTimestamp()); >> + } >> + } >> + } >> + >> + /** >> + * Fetches the to-many fault and the faults of all other objects >> in the EC >> + * that have the same relationship, the fetch timestamp and the >> same class >> + * description.<br> >> + * >> + * @param dbc >> + * database context >> + * @param obj >> + * to-many fault >> + * @return true if it's still a fault. >> + */ >> + private synchronized boolean batchFetchToManyFault >> (EODatabaseContext dbc, Object obj) { >> + if (!fetchingToMany) { >> + fetchingToMany = true; >> + try { >> + EOAccessArrayFaultHandler handler = >> (EOAccessArrayFaultHandler) >> EOFaultHandler.handlerForFault(obj); >> + EOGlobalID source = handler.sourceGlobalID(); >> + EOEditingContext ec = handler.editingContext(); >> + EOEnterpriseObject sourceEO = ec.faultForGlobalID >> (source, ec); >> + if (sourceEO instanceof >> AutoBatchFaultingEnterpriseObject) { >> + String key = handler.relationshipName(); >> + EOEntityClassDescription cd = >> (EOEntityClassDescription) >> sourceEO.classDescription(); >> + EORelationship relationship = cd.entity >> ().relationshipNamed >> (key); >> + if (shouldBatchFetch(sourceEO.entityName(), >> relationship)) { >> + long timestamp = >> ((AutoBatchFaultingEnterpriseObject) >> sourceEO).batchFaultingTimeStamp(); >> + NSMutableArray<EOEnterpriseObject> eos = new >> NSMutableArray<EOEnterpriseObject>(); >> + for (EOEnterpriseObject eo : >> (NSArray<EOEnterpriseObject>) >> ec.registeredObjects()) { >> + if (eo instanceof >> AutoBatchFaultingEnterpriseObject) { >> + if >> (((AutoBatchFaultingEnterpriseObject) >> eo).batchFaultingTimeStamp() == timestamp) { >> + if (!EOFaultHandler.isFault >> (eo) && eo.classDescription() >> == sourceEO.classDescription()) { >> + if (EOFaultHandler.isFault >> (eo.storedValueForKey(key))) { >> + eos.addObject(eo); >> + if (eos.count() == >> autoBatchFetchSize()) { >> + break; >> + } >> + } >> + } >> + } >> + } >> + } >> + if (eos.count() > 1) { >> + // dbc.batchFetchRelationship >> (relationship, eos, ec); >> + >> ERXEOAccessUtilities.batchFetchRelationship(dbc, >> relationship, eos, ec, true); >> + log.info("Fetched to-many " + >> relationship.destinationEntity >> ().name() + " " + eos.count() + " for " + key); >> + return EOFaultHandler.isFault(obj); >> + } >> + } >> + } >> + } >> + finally { >> + fetchingToMany = false; >> + } >> + } >> + return true; >> + } >> + >> + /** >> + * Fetches the to-one fault and the faults of all other >> objects in >> the EC >> + * that have the same relationship, the fetch timestamp and the >> same class >> + * description.<br> >> + * >> + * @param dbc >> + * database context >> + * @param obj >> + * to-one fault >> + * @return true if it's still a fault. >> + */ >> + >> + private synchronized boolean batchFetchToOneFault >> (EODatabaseContext dbc, AutoBatchFaultingEnterpriseObject eo) { >> + if(!fetchingToOne) { >> + fetchingToOne = true; >> + try { >> + EOGlobalID sourceGID = >> eo.batchFaultingSourceGlobalID(); >> + String key = eo.batchFaultingRelationshipName(); >> + if(sourceGID != null && key != null) { >> + EOEditingContext ec = eo.editingContext(); >> + AutoBatchFaultingEnterpriseObject source = >> (AutoBatchFaultingEnterpriseObject) ec.faultForGlobalID(sourceGID, >> ec); >> + EOEntityClassDescription cd = >> (EOEntityClassDescription) >> source.classDescription(); >> + EORelationship relationship = cd.entity >> ().relationshipNamed >> (key); >> + if(shouldBatchFetch(source.entityName(), >> relationship) && ! >> relationship.isToMany()) { >> + long timeStamp = >> source.batchFaultingTimeStamp(); >> + NSMutableArray<EOEnterpriseObject> eos = new >> NSMutableArray<EOEnterpriseObject>(); >> + for (EOEnterpriseObject current : >> (NSArray<EOEnterpriseObject>)ec.registeredObjects()) { >> + if (current instanceof >> AutoBatchFaultingEnterpriseObject) { >> + AutoBatchFaultingEnterpriseObject >> currentEO = >> (AutoBatchFaultingEnterpriseObject) current; >> + if(source.classDescription() == >> currentEO.classDescription >> ()) { >> + if(EOFaultHandler.isFault >> (currentEO.storedValueForKey >> (key))) { >> + if >> (currentEO.batchFaultingTimeStamp() == timeStamp) { >> + eos.addObject >> (currentEO); >> + if(eos.count() == >> autoBatchFetchSize()) { >> + break; >> + } >> + } >> + } >> + } >> + } >> + } >> + if(eos.count() > 1) { >> + >> ERXEOAccessUtilities.batchFetchRelationship(dbc, >> relationship, eos, ec, true); >> + // dbc.batchFetchRelationship >> (relationship, eos, ec); >> + log.info("Fetched to-one " + >> relationship.destinationEntity >> ().name() + " " + eos.count() + " for " + key); >> + return EOFaultHandler.isFault(eo); >> + } >> + } >> + } >> + } finally { >> + fetchingToOne = false; >> + } >> + } >> + return true; >> + } >> + >> + /** >> + * Override this to skip fetching for the given entity and >> relationship. The >> + * default is to skip abstract destination entities. You might >> want to include very large destinations and so on. >> + * >> + * @param entityName >> + * @param relationship >> + * @return true if batch fetching should proceed (the default) >> + */ >> + protected boolean shouldBatchFetch(String entityName, >> EORelationship relationship) { >> + return !relationship.destinationEntity().isAbstractEntity(); >> + } >> } >> >> Modified: trunk/Wonder/Frameworks/Core/ERExtensions/Sources/er/ >> extensions/eof/ERXGenericRecord.java >> =================================================================== >> --- trunk/Wonder/Frameworks/Core/ERExtensions/Sources/er/extensions/ >> eof/ERXGenericRecord.java 2009-09-17 05:06:20 UTC (rev 9873) >> +++ trunk/Wonder/Frameworks/Core/ERExtensions/Sources/er/extensions/ >> eof/ERXGenericRecord.java 2009-09-17 10:38:44 UTC (rev 9874) >> @@ -32,11 +32,11 @@ >> import com.webobjects.foundation.NSKeyValueCoding; >> import com.webobjects.foundation.NSMutableArray; >> import com.webobjects.foundation.NSMutableDictionary; >> -import com.webobjects.foundation.NSSelector; >> import com.webobjects.foundation.NSValidation; >> >> import er.extensions.ERXExtensions; >> import er.extensions.crypting.ERXCrypto; >> +import >> er.extensions.eof.ERXDatabaseContextDelegate.AutoBatchFaultingEnterpriseObject >> ; >> import er.extensions.foundation.ERXArrayUtilities; >> import er.extensions.foundation.ERXDictionaryUtilities; >> import er.extensions.foundation.ERXProperties; >> @@ -77,7 +77,7 @@ >> * on, you must set the system default >> * >> < >> code >>> er.extensions.ERXEnterpriseObject.updateInverseRelationships=true</ >> code>. >> */ >> -public class ERXGenericRecord extends EOGenericRecord implements >> ERXGuardedObjectInterface, ERXGeneratesPrimaryKeyInterface, >> ERXEnterpriseObject, ERXKey.ValueCoding { >> +public class ERXGenericRecord extends EOGenericRecord implements >> ERXGuardedObjectInterface, ERXGeneratesPrimaryKeyInterface, >> ERXEnterpriseObject, ERXKey.ValueCoding, >> AutoBatchFaultingEnterpriseObject { >> >> /** holds all subclass related Logger's */ >> private static NSMutableDictionary<Class, Logger> classLogs = new >> NSMutableDictionary<Class, Logger>(); >> @@ -146,6 +146,11 @@ >> return null; >> } >> >> + /** >> + * Special binding for localized key support. >> + * @author ak >> + * >> + */ >> public static class LocalizedBinding extends >> NSKeyValueCoding._KeyBinding { >> >> public LocalizedBinding(String key) { >> @@ -180,19 +185,45 @@ >> } >> } >> >> - @Override >> - public NSKeyValueCoding._KeyBinding _otherStorageBinding(String >> key) { >> - NSKeyValueCoding._KeyBinding result; >> - String localizedKey = localizedKey(key); >> - if (localizedKey != null) { >> - result = new LocalizedBinding(key); >> - } >> - else { >> - result = super._otherStorageBinding(key); >> - } >> - return result; >> - } >> + /** >> + * Special binding that touches the target of a relationship. >> Needed for automatic batch faulting. >> + * @author ak >> + * >> + */ >> + public static class TouchingBinding extends >> _LazyDictionaryBinding { >> >> + public TouchingBinding(String key) { >> + super(key); >> + } >> + >> + @Override >> + public Object valueInObject(Object object) { >> + Object result = super.valueInObject(object); >> + if(result instanceof AutoBatchFaultingEnterpriseObject >> && EOFaultHandler.isFault(result)) { >> + AutoBatchFaultingEnterpriseObject eo = >> (AutoBatchFaultingEnterpriseObject)object; >> + AutoBatchFaultingEnterpriseObject target = >> (AutoBatchFaultingEnterpriseObject)result; >> + target.touchFromBatchFaultingSource(eo, key()); >> + } >> + return result; >> + } >> + } >> + >> + >> + @Override >> + public NSKeyValueCoding._KeyBinding _otherStorageBinding(String >> key) { >> + NSKeyValueCoding._KeyBinding result; >> + String localizedKey = localizedKey(key); >> + if (classDescription().toOneRelationshipKeys >> ().containsObject >> (key)) { >> + result = new TouchingBinding(key); >> + } else if (localizedKey != null) { >> + result = new LocalizedBinding(key); >> + } >> + else { >> + result = super._otherStorageBinding(key); >> + } >> + return result; >> + } >> + >> /** >> * Clazz object implementation for ERXGenericRecord. See >> * {@link EOEnterpriseObjectClazz} for more information on this >> neat design >> @@ -1568,4 +1599,61 @@ >> } >> } >> } >> + >> + /** >> + * Last fetch time >> + */ >> + private long _fetchTime; >> + >> + /** >> + * Which key touched us >> + */ >> + private String _touchKey; >> + >> + /** >> + * Which GID touched us >> + */ >> + public EOGlobalID _touchSource; >> + >> + /** >> + * The fetch time for this object >> + * @return fetch time >> + */ >> + public long batchFaultingTimeStamp() { >> + return _fetchTime; >> + } >> + >> + /** >> + * The source EO that touched us >> + * @return gid of the source >> + */ >> + public EOGlobalID batchFaultingSourceGlobalID() { >> + return _touchSource; >> + } >> + >> + /** >> + * The key that touched us >> + * @return relationship name >> + */ >> + public String batchFaultingRelationshipName() { >> + return _touchKey; >> + } >> + >> + /** >> + * Touches this EO with the given source and the given key. >> Stores GID and timestamp. >> + * @param toucher >> + * @param key >> + */ >> + public void touchFromBatchFaultingSource >> (AutoBatchFaultingEnterpriseObject toucher, String key) { >> + _touchKey = key; >> + _touchSource = toucher.editingContext().globalIDForObject >> (toucher); >> + } >> + >> + /** >> + * Touches this EO from a fetch. Note that this is the last >> fetch, not when the object has been initialized. >> + * @param timestamp >> + */ >> + public void setBatchFaultingTimestamp(long timestamp) { >> + _fetchTime = timestamp; >> + } >> } >> >> >> This was sent by the SourceForge.net collaborative development >> platform, the world's largest Open Source development site. >> >> --- >> --- >> --- >> --------------------------------------------------------------------- >> Come build with us! The BlackBerry® Developer Conference in SF, >> CA >> is the only developer event you need to attend this year. Jumpstart >> your >> developing skills, take BlackBerry mobile applications to market and >> stay >> ahead of the curve. Join us from November 9-12, 2009. Register >> now! >> http://p.sf.net/sfu/devconf >> _______________________________________________ >> Wonder-cvs mailing list >> Won...@li... >> https://lists.sourceforge.net/lists/listinfo/wonder-cvs > > with regards, > -- > > Lachlan Deck > > > > > --- > --- > --- > --------------------------------------------------------------------- > Come build with us! The BlackBerry® Developer Conference in SF, CA > is the only developer event you need to attend this year. Jumpstart > your > developing skills, take BlackBerry mobile applications to market and > stay > ahead of the curve. Join us from November 9-12, 2009. Register > now! > http://p.sf.net/sfu/devconf > _______________________________________________ > Wonder-cvs mailing list > Won...@li... > https://lists.sourceforge.net/lists/listinfo/wonder-cvs |