summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java
diff options
context:
space:
mode:
Diffstat (limited to 'projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java')
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java762
1 files changed, 762 insertions, 0 deletions
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java
new file mode 100644
index 0000000..ddaacf5
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java
@@ -0,0 +1,762 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, see http://www.gnu.org
+*/
+
+package net.wotonomy.control;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSNotification;
+import net.wotonomy.foundation.NSNotificationCenter;
+import net.wotonomy.foundation.NSNotificationQueue;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* An abstract implementation of object store that
+* implements common functionality. Subclasses must
+* implement data object creation, initialization, and
+* refault logic, as well as logic to commit an editing
+* context.
+*/
+public abstract class AbstractObjectStore extends EOObjectStore
+{
+ private NSMutableArray insertedIDsBuffer;
+ private NSMutableArray updatedIDsBuffer;
+ private NSMutableArray deletedIDsBuffer;
+ private NSMutableArray invalidatedIDsBuffer;
+
+ private Map snapshots;
+ private List exceptionList;
+
+ /**
+ * Constructs a new instance of this object store.
+ */
+ public AbstractObjectStore()
+ {
+ snapshots = new HashMap();
+ exceptionList = null;
+
+ insertedIDsBuffer = new NSMutableArray();
+ updatedIDsBuffer = new NSMutableArray();
+ deletedIDsBuffer = new NSMutableArray();
+ invalidatedIDsBuffer = new NSMutableArray();
+
+ // register for notifications
+ NSSelector handleNotification =
+ new NSSelector( "handleNotification",
+ new Class[] { NSNotification.class } );
+ NSNotificationCenter.defaultCenter().addObserver(
+ this,
+ handleNotification,
+ EOClassDescription.ClassDescriptionNeededForEntityNameNotification,
+ null );
+ }
+
+ /**
+ * This implementation returns an appropriately configured array fault.
+ */
+ public NSArray arrayFaultWithSourceGlobalID( EOGlobalID aGlobalID,
+ String aRelationship, EOEditingContext aContext )
+ { // System.out.println( "arrayFaultWithSourceGlobalID: " + aGlobalID + " : " + aRelationship );
+ return new ArrayFault(
+ aGlobalID, aRelationship, aContext );
+ }
+
+ /**
+ * This implementation returns the actual object for the specified id.
+ */
+ public /*EOEnterpriseObject*/Object faultForGlobalID( EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ { // System.out.println( "faultForGlobalID: " + aGlobalID );
+ return /*(EOEnterpriseObject)*/createInstanceWithEditingContext( aGlobalID, aContext );
+ }
+
+ /**
+ * Returns a fault representing an object of the specified entity type with
+ * values from the specified dictionary. The fault should belong to the
+ * specified editing context.
+ * NOTE: Faults are not supported yet.
+ */
+ public /*EOEnterpriseObject*/Object faultForRawRow( Map aDictionary, String anEntityName,
+ EOEditingContext aContext )
+ {
+ //TODO: raw rows are not yet supported
+ throw new WotonomyException( "Faults are not yet supported." );
+ }
+
+ /**
+ * Given a newly instantiated object, this method initializes its
+ * properties to values appropriate for the specified id. The object
+ * should belong to the specified editing context. This method is called
+ * to populate faults.
+ */
+ public void initializeObject( Object anObject, EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ { //System.out.println( "initializeObject: " + aGlobalID );
+ try
+ {
+ String entity = entityForGlobalIDOrObject( aGlobalID, null );
+ EOClassDescription classDesc =
+ EOClassDescription.classDescriptionForEntityName( entity );
+ if ( classDesc == null )
+ {
+ throw new WotonomyException( "Unknown entity type: " + entity );
+ }
+
+ Collection attributes = classDesc.attributeKeys();
+ Map data = readFromCache( aGlobalID, attributes );
+ String key;
+ Iterator iterator = attributes.iterator();
+ while ( iterator.hasNext() )
+ {
+ key = iterator.next().toString();
+
+ // write the snapshot's reference into the object
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)anObject).takeStoredValueForKey( data.get( key ), key );
+ }
+ else
+ {
+ EOKeyValueCodingSupport.takeStoredValueForKey( anObject, data.get( key ), key );
+ }
+
+ //NOTE: our objects are expected to make a copy
+ // of their data before it is modified, so it's okay
+ // to return them our copy of the data:
+ // we trust that they will not modify it.
+ }
+ }
+ catch ( Exception exc )
+ {
+ exc.printStackTrace();
+ }
+ }
+
+ /**
+ * Reads the local data snapshot for the specified id.
+ * If no snapshot exists, a new snapshot is created.
+ * If the specified keys are not in the snapshot,
+ * new data is fetched into the snapshot.
+ * If null is specified, all known keys are returned.
+ * Will not return null.
+ * Result will have values for those keys and only
+ * those keys requested. Missing keys indicate an
+ * error occurred.
+ */
+ protected Map readFromCache( EOGlobalID aGlobalID, Collection keys )
+ {
+ Map snapshot = (Map) snapshots.get( aGlobalID );
+
+ // if no snapshot for this id, create an empty one
+ if ( snapshot == null )
+ {
+ snapshot = new HashMap();
+ snapshots.put( aGlobalID, snapshot );
+ }
+
+ // if we don't have all the necessary keys
+ if ( ( keys == null ) || ( ! snapshot.keySet().containsAll( keys ) ) )
+ {
+ // we need to make a server call
+ try
+ {
+ Map data = readObject( aGlobalID, keys );
+
+ // compare timestamps
+ Comparable localTimestamp = (Comparable) timestampForData( snapshot );
+ // if our local snapshot has an timestamp (new snapshots don't have timestamp)
+ if ( localTimestamp != null )
+ {
+ Comparable incomingTimestamp = (Comparable) timestampForData( data );
+ if ( incomingTimestamp == null )
+ {
+ // not allowed to happen
+ new RuntimeException( "Server returned data without an timestamp" ).printStackTrace();
+ // however, we can just assume it's a newer timestamp and continue
+ }
+
+ // if timestamps don't match
+ if ( ( incomingTimestamp == null ) || ( ! incomingTimestamp.equals( localTimestamp ) ) )
+ {
+ // dump our existing snapshot's data
+ snapshot.clear();
+ // queue for a notification on this oid as updated
+ //TODO: implement this
+ }
+ }
+
+ // copy new data into our local snapshot
+ snapshot.putAll( data );
+ }
+ catch ( Exception exc )
+ {
+ exc.printStackTrace();
+ }
+ }
+
+ // return just the requested keys from our updated snapshot
+ Map result = new HashMap();
+ if ( keys == null )
+ {
+ result.putAll( snapshot );
+ }
+ else
+ {
+ Object key;
+ Iterator iterator = keys.iterator();
+ while ( iterator.hasNext() )
+ {
+ key = iterator.next();
+ result.put( key, snapshot.get( key ) );
+ }
+ }
+ return snapshot;
+ }
+
+ /**
+ * Returns a comparable object (typically a Date or Long) for
+ * the given data map or snapshot. This is used to determine
+ * whether a local snapshot should be dumped in favor of fetched
+ * data from the server.
+ * Returns null if no timestamp can be determined, in which
+ * case the fetched data will assumed to be more recent than
+ * any local snapshot.
+ */
+ abstract protected Comparable timestampForData( Map aDataMap );
+
+ /**
+ * Extracts the global id for the fetched data or snapshot.
+ * Some entities have multi-attribute keys that would be
+ * assembled into a single instance of EOGlobalID.
+ */
+ abstract protected EOGlobalID globalIDForData( Map aDataMap );
+
+ /**
+ * Returns the entity that corresponds to the specified global id
+ * and/or object. Either may be null, but both will not be null.
+ * //FIXME: This is less than elegant.
+ */
+ abstract protected String entityForGlobalIDOrObject(
+ EOGlobalID aGlobalID, Object anObject );
+
+ /**
+ * Returns the keys that have changed on the specified object.
+ * If null, all keys are presumed changed, including relationships.
+ */
+ abstract protected Collection changedKeysForObject( Object anObject );
+
+ /**
+ * Returns the data for the row corresponding to the specified id
+ * containing at least the specified keys. Implementations are allowed
+ * to return more data than requested, and callers are advised to take
+ * advantage of the returned data.
+ */
+ abstract protected Map readObject( EOGlobalID aGlobalID, Collection keys );
+
+ /**
+ * Returns the data for the row corresponding to the specified id.
+ * //TODO: Need a better return value? How to return invalidated list?
+ */
+ abstract protected Map insertObject( EOGlobalID aGlobalID, Map aDataMap );
+
+ /**
+ * Returns the data for the row corresponding to the specified id.
+ * //TODO: Need a better return value? How to return invalidated list?
+ */
+ abstract protected Object updateObject( EOGlobalID aGlobalID, Map aDataMap );
+
+ /**
+ * Returns the data for the row corresponding to the specified id.
+ * //TODO: Need a better return value? How to return invalidated list?
+ */
+ abstract protected Object deleteObject( EOGlobalID aGlobalID );
+
+ /**
+ * Creates a new instance of an object that corresponds to the
+ * specified global id and is registered in the specified context.
+ * This implementation extracts the entity type from getEntityForGlobaID
+ * and construct a new instance from the class description that
+ * corresponds to the entity type. Override to change this behavior.
+ */
+ protected Object createInstanceWithEditingContext(
+ EOGlobalID aGlobalID, EOEditingContext aContext )
+ {
+ String entity = entityForGlobalIDOrObject( aGlobalID, null );
+ EOClassDescription classDesc =
+ EOClassDescription.classDescriptionForEntityName( entity );
+ if ( classDesc == null )
+ {
+ throw new WotonomyException( "Unknown entity type: " + entity );
+ }
+
+ Object result = classDesc.createInstanceWithEditingContext( aContext, aGlobalID );
+ if ( result instanceof EOFaulting )
+ {
+ ((EOFaulting)result).turnIntoFault( null );
+ }
+ return result;
+ }
+
+ /**
+ * Dumps the snapshot corresponding to the specified id.
+ */
+ protected void invalidateObject( EOGlobalID aGlobalID )
+ {
+ snapshots.remove( aGlobalID );
+ }
+
+ /**
+ * Dumps all snapshots.
+ */
+ protected void invalidateAllCache()
+ {
+ snapshots.clear();
+ }
+
+ /**
+ * Remove all values from all objects in memory, turning them into faults,
+ * and posts a notification that all objects have been invalidated.
+ */
+ public void invalidateAllObjects()
+ {
+ invalidateAllCache();
+
+ // post notification
+ NSNotificationQueue.defaultQueue().enqueueNotification(
+ new NSNotification(
+ InvalidatedAllObjectsInStoreNotification, this ),
+ NSNotificationQueue.PostNow );
+ }
+
+ /**
+ * Removes values with the specified ids from memory, turning them into
+ * faults, and posts a notification that those objects have been invalidated.
+ */
+ public void invalidateObjectsWithGlobalIDs( List aList )
+ {
+ NSArray empty = new NSArray();
+ NSMutableArray invalidated = new NSMutableArray();
+
+ Object object;
+ Iterator iterator = aList.iterator();
+ while ( iterator.hasNext() )
+ {
+ object = iterator.next();
+ invalidateObject( (EOGlobalID) object );
+ invalidated.addObject( object );
+ }
+
+ NSMutableDictionary info = new NSMutableDictionary();
+ info.setObjectForKey( empty, InsertedKey );
+ info.setObjectForKey( empty, UpdatedKey );
+ info.setObjectForKey( empty, DeletedKey );
+ info.setObjectForKey( invalidated, InvalidatedKey );
+
+ // post notification
+ NSNotificationQueue.defaultQueue().
+ enqueueNotificationWithCoalesceMaskForModes( new NSNotification(
+ ObjectsChangedInStoreNotification, this, info ),
+ NSNotificationQueue.PostNow,
+ NSNotificationQueue.NotificationNoCoalescing, null );
+ }
+
+ /**
+ * Returns false because locking is not currently permitted.
+ */
+ public boolean isObjectLockedWithGlobalID( EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ return false;
+ }
+
+ /**
+ * Does nothing because locking is not currently permitted.
+ */
+ public void lockObjectWithGlobalID( EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ // does nothing
+ }
+
+ /**
+ * Returns a List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship. This method may not return an array fault
+ * because array faults call this method to fetch on demand.
+ * All objects must be registered the specified editing context.
+ * The specified relationship key must produce a result of
+ * type Collection for the source object or an exception is thrown.
+ */
+ public NSArray objectsForSourceGlobalID( EOGlobalID aGlobalID,
+ String aRelationship, EOEditingContext aContext )
+ { // System.out.println( "objectsForSourceGlobalID: " + aGlobalID + " : " + aRelationship + " : " );
+
+ Map snapshot = readFromCache( aGlobalID, new NSArray( aRelationship ) );
+ Object value = snapshot.get( aRelationship );
+ if ( value == null ) value = new NSArray(); // empty list
+ if ( ! ( value instanceof Collection ) )
+ {
+ throw new RuntimeException( "Specified relationship is not a collection: "
+ + aRelationship + " : " + aGlobalID + " : " + value );
+ }
+
+ NSArray result = new NSMutableArray();
+
+ // get fault for each id
+ EOGlobalID id;
+ Object fault;
+ Iterator iterator = ((Collection)value).iterator();
+ while ( iterator.hasNext() )
+ {
+ id = (EOGlobalID) iterator.next();
+
+ // get registered fault
+ fault = aContext.faultForGlobalID( id, aContext );
+
+ // assert fault
+ if ( fault == null )
+ {
+ // this should never happen
+ throw new RuntimeException(
+ "Could not find fault for ID: " + id );
+ }
+
+ result.add( fault );
+ }
+
+ fireObjectsChangedInStore();
+
+//System.out.println( "done" );
+ return result;
+ }
+
+ /**
+ * Returns a List of objects the meet the criteria of
+ * the supplied specification. Faults are not allowed in the array.
+ * Each object is registered with the specified editing context.
+ * If any object is already fetched in the specified context,
+ * it is not refetched and that object should be used in the array.
+ */
+ public NSArray objectsWithFetchSpecification(
+ EOFetchSpecification aFetchSpec, EOEditingContext aContext )
+ {
+ NSMutableArray result = new NSMutableArray();
+
+ //TODO: implement this
+
+ return result;
+ }
+
+ /**
+ * Fires ObjectsChangedInStoreNotification
+ * with contents of buffers and then clears buffers.
+ * If buffers are empty, does nothing.
+ */
+ private void fireObjectsChangedInStore()
+ {
+ // check for changes to broadcast
+ if ( insertedIDsBuffer.size() + updatedIDsBuffer.size() +
+ deletedIDsBuffer.size() + invalidatedIDsBuffer.size() == 0 )
+ {
+ return;
+ }
+
+ // broadcast ObjectsChangedInStoreNotification
+ // for the benefit of child editing contexts
+
+ NSMutableDictionary storeInfo = new NSMutableDictionary();
+
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) insertedIDsBuffer ),
+ EOObjectStore.InsertedKey );
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) updatedIDsBuffer ),
+ EOObjectStore.UpdatedKey );
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) deletedIDsBuffer ),
+ EOObjectStore.DeletedKey );
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) invalidatedIDsBuffer ),
+ EOObjectStore.InvalidatedKey );
+
+ // clear buffers
+
+ insertedIDsBuffer.removeAllObjects();
+ updatedIDsBuffer.removeAllObjects();
+ deletedIDsBuffer.removeAllObjects();
+ invalidatedIDsBuffer.removeAllObjects();
+
+ // post notification
+ NSNotificationQueue.defaultQueue().
+ enqueueNotificationWithCoalesceMaskForModes( new NSNotification(
+ ObjectsChangedInStoreNotification, this, storeInfo ),
+ NSNotificationQueue.PostNow,
+ NSNotificationQueue.NotificationNoCoalescing, null );
+ }
+
+ /**
+ * Removes all values from the specified object,
+ * converting it into a fault for the specified id.
+ * New or deleted objects should not be refaulted.
+ */
+ public void refaultObject( Object anObject, EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+//System.out.println( "refaultObject: " + aGlobalID );
+//new net.wotonomy.ui.swing.util.StackTraceInspector();
+ if ( anObject instanceof EOFaulting )
+ {
+ ((EOFaulting)anObject).turnIntoFault( null );
+ }
+ }
+
+ /**
+ * Writes all changes in the specified editing context
+ * to the respository.
+ */
+ public void saveChangesInEditingContext ( EOEditingContext aContext )
+ {
+ Object result; // need a container result?
+ Map updateMap;
+ Object object;
+ EOGlobalID id;
+ Iterator iterator;
+
+ //TODO: the ordering of operations here
+ // needs to be a lot more sophisticated.
+
+ // process deletes first
+ iterator = aContext.deletedObjects().iterator();
+ while ( iterator.hasNext() )
+ {
+ object = iterator.next();
+ id = aContext.globalIDForObject( object );
+ try
+ {
+ result = deleteObject( id );
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error deleting object: " + id );
+ exc.printStackTrace();
+ }
+ }
+
+ // process inserts next
+ iterator = aContext.insertedObjects().iterator();
+ while ( iterator.hasNext() )
+ {
+ object = iterator.next();
+ processInsert( aContext, object );
+ }
+
+ // process updates last
+ iterator = aContext.updatedObjects().iterator();
+ while ( iterator.hasNext() )
+ {
+ object = iterator.next();
+ id = aContext.globalIDForObject( object );
+ try
+ {
+ updateMap = getUpdateMap( aContext, object );
+ result = updateObject( id, updateMap );
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error updating object: " + id );
+ exc.printStackTrace();
+ }
+ }
+
+ //aContext.invalidateAllObjects();
+ }
+
+ protected Object processInsert( EOEditingContext aContext, Object object )
+ {
+ Map result = null;
+ EOGlobalID id = aContext.globalIDForObject( object );
+ try
+ {
+ Map updateMap;
+ updateMap = getUpdateMap( aContext, object );
+ result = insertObject( id, updateMap );
+ id = globalIDForData( result ); // read new permanent id
+
+ // broadcast that the global id has changed.
+ NSMutableDictionary userInfo = new NSMutableDictionary();
+ userInfo.setObjectForKey( id, aContext.globalIDForObject( object ) );
+ NSNotificationQueue.defaultQueue().enqueueNotification(
+ new NSNotification( EOGlobalID.GlobalIDChangedNotification,
+ null , userInfo ), NSNotificationQueue.PostNow );
+
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error inserting object: " + id );
+ exc.printStackTrace();
+ }
+ return result;
+ }
+
+ /**
+ * This method returns a map containing just the keys that are modified
+ * for a given object, converting any to-one or to-many relationships
+ * to id references.
+ */
+ protected Map getUpdateMap( EOEditingContext aContext, Object anObject )
+ {
+ Map result = new HashMap();
+ EOEditingContext context = aContext;
+
+ String entity = entityForGlobalIDOrObject( null, anObject );
+ EOClassDescription classDesc =
+ EOClassDescription.classDescriptionForEntityName( entity );
+ if ( classDesc == null )
+ {
+ throw new WotonomyException( "Unknown entity type: " + entity );
+ }
+
+ NSArray oneKeys = classDesc.toOneRelationshipKeys();
+ NSArray manyKeys = classDesc.toManyRelationshipKeys();
+
+ String key;
+ Object value;
+ EOGlobalID id;
+
+ Collection changedKeys = changedKeysForObject( anObject );
+ if ( changedKeys == null )
+ {
+ // assume all keys changed
+ changedKeys = classDesc.attributeKeys();
+ changedKeys.addAll( oneKeys );
+ changedKeys.addAll( manyKeys );
+ }
+ Iterator iterator = changedKeys.iterator();
+ while ( iterator.hasNext() )
+ {
+ key = iterator.next().toString();
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ value = ((EOKeyValueCoding)anObject).storedValueForKey( key );
+ }
+ else
+ {
+ value = EOKeyValueCodingSupport.storedValueForKey( anObject, key );
+ }
+
+ // convert to-one relationship to oid
+ if ( oneKeys.contains( key ) )
+ {
+ id = context.globalIDForObject( value );
+
+ // if this id hasn't been persisted, save it first
+ // NOTE: this won't work for self-referential graphs of objects!
+ if ( id.isTemporary() )
+ {
+ processInsert( aContext, value );
+ id = context.globalIDForObject( value );
+ }
+
+ value = id;
+ }
+ else
+ // convert to-many relationship list to oid list
+ if ( manyKeys.contains( key ) )
+ {
+ //NOTE: we can assume that array faults that
+ // are marked as changed have been fired.
+ if ( value instanceof Collection )
+ {
+ Object object;
+ Collection newValue = new LinkedList();
+ Iterator jiterator = ((Collection)value).iterator();
+ while ( jiterator.hasNext() )
+ {
+ object = jiterator.next();
+ id = context.globalIDForObject( object );
+
+ // if this id hasn't been persisted, save it first
+ // NOTE: this won't work for self-referential graphs of objects!
+ if ( id.isTemporary() )
+ {
+ processInsert( aContext, object );
+ id = context.globalIDForObject( object );
+ }
+ newValue.add( id );
+ }
+ value = newValue;
+ }
+ else
+ {
+ // should never happen
+ new RuntimeException(
+ "Can't update to-many relationship because it's not a Collection." )
+ .printStackTrace();
+ }
+ }
+
+ // place value in map
+ result.put( key, value );
+ }
+
+System.out.println( result );
+ return result;
+ }
+
+/*
+ * $Log$
+ * Revision 1.2 2006/02/16 16:47:14 cgruber
+ * Move some classes in to "internal" packages and re-work imports, etc.
+ *
+ * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions.
+ *
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.5 2003/12/18 15:37:38 mpowers
+ * Changes to retain ability to work with objects that don't necessarily
+ * implement EOEnterpriseObject. I would still like to preserve this case
+ * for general usage, however the access package is free to assume that
+ * those objects will be EOs and cast appropriately.
+ *
+ * Revision 1.4 2003/08/19 01:53:12 chochos
+ * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now.
+ *
+ * Revision 1.3 2002/10/24 18:18:12 mpowers
+ * NSArray's are now considered read-only, so we can return our internal
+ * representation to reduce unnecessary object allocation.
+ *
+ * Revision 1.2 2002/01/19 17:27:49 mpowers
+ * Implemented most of it.
+ *
+ * Revision 1.1 2001/11/25 22:44:02 mpowers
+ * Contributing draft of AbstractObjectStore.
+ *
+ *
+ */
+}
+