Coverage Report - net.wotonomy.control.AbstractObjectStore
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractObjectStore
0% 
0% 
2.571
 
 1  
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2001 Michael Powers
 4  
 
 5  
 This library is free software; you can redistribute it and/or
 6  
 modify it under the terms of the GNU Lesser General Public
 7  
 License as published by the Free Software Foundation; either
 8  
 version 2.1 of the License, or (at your option) any later version.
 9  
 
 10  
 This library is distributed in the hope that it will be useful,
 11  
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 Lesser General Public License for more details.
 14  
 
 15  
 You should have received a copy of the GNU Lesser General Public
 16  
 License along with this library; if not, see http://www.gnu.org
 17  
 */
 18  
 
 19  
 package net.wotonomy.control;
 20  
 
 21  
 import java.util.Collection;
 22  
 import java.util.HashMap;
 23  
 import java.util.Iterator;
 24  
 import java.util.LinkedList;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 
 28  
 import net.wotonomy.foundation.NSArray;
 29  
 import net.wotonomy.foundation.NSMutableArray;
 30  
 import net.wotonomy.foundation.NSMutableDictionary;
 31  
 import net.wotonomy.foundation.NSNotification;
 32  
 import net.wotonomy.foundation.NSNotificationCenter;
 33  
 import net.wotonomy.foundation.NSNotificationQueue;
 34  
 import net.wotonomy.foundation.NSSelector;
 35  
 import net.wotonomy.foundation.internal.WotonomyException;
 36  
 
 37  
 /**
 38  
 * An abstract implementation of object store that 
 39  
 * implements common functionality.  Subclasses must
 40  
 * implement data object creation, initialization, and
 41  
 * refault logic, as well as logic to commit an editing 
 42  
 * context.
 43  
 */
 44  
 public abstract class AbstractObjectStore extends EOObjectStore
 45  
 {
 46  
     private NSMutableArray insertedIDsBuffer;
 47  
     private NSMutableArray updatedIDsBuffer;
 48  
     private NSMutableArray deletedIDsBuffer;
 49  
     private NSMutableArray invalidatedIDsBuffer;
 50  
 
 51  
     private Map snapshots;
 52  
     private List exceptionList;
 53  
     
 54  
     /**
 55  
     * Constructs a new instance of this object store.
 56  
     */
 57  0
     public AbstractObjectStore()
 58  0
     {
 59  0
         snapshots = new HashMap();
 60  0
         exceptionList = null;
 61  
         
 62  0
         insertedIDsBuffer = new NSMutableArray();
 63  0
         updatedIDsBuffer = new NSMutableArray();
 64  0
         deletedIDsBuffer = new NSMutableArray();
 65  0
         invalidatedIDsBuffer = new NSMutableArray();
 66  
 
 67  
         // register for notifications
 68  0
         NSSelector handleNotification =
 69  0
             new NSSelector( "handleNotification",
 70  0
                 new Class[] { NSNotification.class } );
 71  0
         NSNotificationCenter.defaultCenter().addObserver(
 72  0
             this,
 73  0
             handleNotification,
 74  0
             EOClassDescription.ClassDescriptionNeededForEntityNameNotification,
 75  0
             null );
 76  0
     }
 77  
 
 78  
     /**
 79  
     * This implementation returns an appropriately configured array fault.
 80  
     */
 81  
     public NSArray arrayFaultWithSourceGlobalID( EOGlobalID aGlobalID,
 82  
                             String aRelationship, EOEditingContext aContext )
 83  
     { // System.out.println( "arrayFaultWithSourceGlobalID: " + aGlobalID + " : " + aRelationship );
 84  0
         return new ArrayFault(
 85  0
             aGlobalID, aRelationship, aContext );
 86  
     }
 87  
 
 88  
     /**
 89  
     * This implementation returns the actual object for the specified id.
 90  
     */
 91  
     public /*EOEnterpriseObject*/Object faultForGlobalID( EOGlobalID aGlobalID,
 92  
                                     EOEditingContext aContext )
 93  
     { // System.out.println( "faultForGlobalID: " + aGlobalID );
 94  0
         return /*(EOEnterpriseObject)*/createInstanceWithEditingContext( aGlobalID, aContext );
 95  
     }
 96  
 
 97  
     /**
 98  
     * Returns a fault representing an object of the specified entity type with
 99  
     * values from the specified dictionary.  The fault should belong to the
 100  
     * specified editing context.
 101  
     * NOTE: Faults are not supported yet.
 102  
     */
 103  
     public /*EOEnterpriseObject*/Object faultForRawRow( Map aDictionary, String anEntityName,
 104  
                                   EOEditingContext aContext )
 105  
     {
 106  
         //TODO: raw rows are not yet supported
 107  0
         throw new WotonomyException( "Faults are not yet supported." );
 108  
     }
 109  
 
 110  
     /**
 111  
     * Given a newly instantiated object, this method initializes its
 112  
     * properties to values appropriate for the specified id.  The object
 113  
     * should belong to the specified editing context. This method is called
 114  
     * to populate faults.
 115  
     */
 116  
     public void initializeObject( Object anObject, EOGlobalID aGlobalID,
 117  
                                   EOEditingContext aContext )
 118  
     { //System.out.println( "initializeObject: " + aGlobalID );
 119  
         try
 120  
         {
 121  0
             String entity = entityForGlobalIDOrObject( aGlobalID, null );
 122  0
             EOClassDescription classDesc = 
 123  0
                 EOClassDescription.classDescriptionForEntityName( entity );
 124  0
             if ( classDesc == null )
 125  
             {
 126  0
                 throw new WotonomyException( "Unknown entity type: " + entity );   
 127  
             }
 128  
         
 129  0
             Collection attributes = classDesc.attributeKeys();
 130  0
             Map data = readFromCache( aGlobalID, attributes );
 131  
             String key;
 132  0
             Iterator iterator = attributes.iterator();
 133  0
             while ( iterator.hasNext() )
 134  
             {
 135  0
                 key = iterator.next().toString();
 136  
                 
 137  
                 // write the snapshot's reference into the object
 138  0
                 if ( anObject instanceof EOKeyValueCoding )
 139  
                 {
 140  0
                     ((EOKeyValueCoding)anObject).takeStoredValueForKey( data.get( key ), key );
 141  0
                 }
 142  
                 else
 143  
                 {
 144  0
                     EOKeyValueCodingSupport.takeStoredValueForKey( anObject, data.get( key ), key );
 145  
                 }
 146  
                 
 147  
                 //NOTE: our objects are expected to make a copy
 148  
                 // of their data before it is modified, so it's okay
 149  
                 // to return them our copy of the data: 
 150  
                 // we trust that they will not modify it.
 151  0
             }
 152  
         }
 153  0
         catch ( Exception exc )
 154  
         {        
 155  0
             exc.printStackTrace();
 156  0
         }
 157  0
     }
 158  
 
 159  
     /**
 160  
     * Reads the local data snapshot for the specified id.
 161  
     * If no snapshot exists, a new snapshot is created.
 162  
     * If the specified keys are not in the snapshot,
 163  
     * new data is fetched into the snapshot.
 164  
     * If null is specified, all known keys are returned.
 165  
     * Will not return null.  
 166  
     * Result will have values for those keys and only
 167  
     * those keys requested.  Missing keys indicate an
 168  
     * error occurred.
 169  
     */
 170  
     protected Map readFromCache( EOGlobalID aGlobalID, Collection keys )
 171  
     {
 172  0
         Map snapshot = (Map) snapshots.get( aGlobalID );
 173  
 
 174  
         // if no snapshot for this id, create an empty one
 175  0
         if ( snapshot == null )
 176  
         {
 177  0
             snapshot = new HashMap();
 178  0
             snapshots.put( aGlobalID, snapshot );
 179  
         }
 180  
 
 181  
         // if we don't have all the necessary keys
 182  0
         if ( ( keys == null ) || ( ! snapshot.keySet().containsAll( keys ) ) )
 183  
         {
 184  
             // we need to make a server call
 185  
             try
 186  
             {
 187  0
                 Map data = readObject( aGlobalID, keys );
 188  
                 
 189  
                 // compare timestamps 
 190  0
                 Comparable localTimestamp = (Comparable) timestampForData( snapshot );
 191  
                 // if our local snapshot has an timestamp (new snapshots don't have timestamp)
 192  0
                 if ( localTimestamp != null )
 193  
                 {
 194  0
                     Comparable incomingTimestamp = (Comparable) timestampForData( data );
 195  0
                     if ( incomingTimestamp == null )
 196  
                     {
 197  
                         // not allowed to happen
 198  0
                         new RuntimeException( "Server returned data without an timestamp" ).printStackTrace();
 199  
                         // however, we can just assume it's a newer timestamp and continue
 200  
                     }
 201  
                     
 202  
                     // if timestamps don't match
 203  0
                     if ( ( incomingTimestamp == null ) || ( ! incomingTimestamp.equals( localTimestamp ) ) )
 204  
                     {
 205  
                         // dump our existing snapshot's data
 206  0
                         snapshot.clear();
 207  
                         // queue for a notification on this oid as updated
 208  
                         //TODO: implement this
 209  
                     }
 210  
                 }
 211  
                     
 212  
                 // copy new data into our local snapshot
 213  0
                 snapshot.putAll( data );
 214  
             }
 215  0
             catch ( Exception exc )
 216  
             {        
 217  0
                 exc.printStackTrace();
 218  0
             }
 219  
         }
 220  
         
 221  
         // return just the requested keys from our updated snapshot
 222  0
         Map result = new HashMap();
 223  0
         if ( keys == null )
 224  
         {
 225  0
             result.putAll( snapshot );
 226  0
         }
 227  
         else
 228  
         {
 229  
             Object key;
 230  0
             Iterator iterator = keys.iterator();
 231  0
             while ( iterator.hasNext() )
 232  
             {
 233  0
                 key = iterator.next();
 234  0
                 result.put( key, snapshot.get( key ) );
 235  0
             }
 236  
         }
 237  0
         return snapshot;
 238  
     }
 239  
     
 240  
     /**
 241  
     * Returns a comparable object (typically a Date or Long) for
 242  
     * the given data map or snapshot.  This is used to determine
 243  
     * whether a local snapshot should be dumped in favor of fetched
 244  
     * data from the server.
 245  
     * Returns null if no timestamp can be determined, in which
 246  
     * case the fetched data will assumed to be more recent than
 247  
     * any local snapshot.
 248  
     */
 249  
     abstract protected Comparable timestampForData( Map aDataMap );
 250  
     
 251  
     /**
 252  
     * Extracts the global id for the fetched data or snapshot.
 253  
     * Some entities have multi-attribute keys that would be 
 254  
     * assembled into a single instance of EOGlobalID.
 255  
     */
 256  
     abstract protected EOGlobalID globalIDForData( Map aDataMap );
 257  
     
 258  
     /**
 259  
     * Returns the entity that corresponds to the specified global id
 260  
     * and/or object.  Either may be null, but both will not be null.
 261  
     * //FIXME: This is less than elegant.
 262  
     */
 263  
     abstract protected String entityForGlobalIDOrObject( 
 264  
         EOGlobalID aGlobalID, Object anObject );
 265  
     
 266  
     /**
 267  
     * Returns the keys that have changed on the specified object.
 268  
     * If null, all keys are presumed changed, including relationships.
 269  
     */
 270  
     abstract protected Collection changedKeysForObject( Object anObject );
 271  
     
 272  
     /**
 273  
     * Returns the data for the row corresponding to the specified id
 274  
     * containing at least the specified keys.  Implementations are allowed
 275  
     * to return more data than requested, and callers are advised to take
 276  
     * advantage of the returned data.
 277  
     */
 278  
     abstract protected Map readObject( EOGlobalID aGlobalID, Collection keys );
 279  
     
 280  
     /**
 281  
     * Returns the data for the row corresponding to the specified id.
 282  
     * //TODO: Need a better return value?  How to return invalidated list?
 283  
     */
 284  
     abstract protected Map insertObject( EOGlobalID aGlobalID, Map aDataMap );
 285  
     
 286  
     /**
 287  
     * Returns the data for the row corresponding to the specified id.
 288  
     * //TODO: Need a better return value?  How to return invalidated list?
 289  
     */
 290  
     abstract protected Object updateObject( EOGlobalID aGlobalID, Map aDataMap );
 291  
     
 292  
     /**
 293  
     * Returns the data for the row corresponding to the specified id.
 294  
     * //TODO: Need a better return value?  How to return invalidated list?
 295  
     */
 296  
     abstract protected Object deleteObject( EOGlobalID aGlobalID );
 297  
     
 298  
     /**
 299  
     * Creates a new instance of an object that corresponds to the
 300  
     * specified global id and is registered in the specified context.
 301  
     * This implementation extracts the entity type from getEntityForGlobaID
 302  
     * and construct a new instance from the class description that 
 303  
     * corresponds to the entity type.  Override to change this behavior.
 304  
     */
 305  
     protected Object createInstanceWithEditingContext( 
 306  
         EOGlobalID aGlobalID, EOEditingContext aContext )
 307  
     {
 308  0
         String entity = entityForGlobalIDOrObject( aGlobalID, null );
 309  0
         EOClassDescription classDesc = 
 310  0
             EOClassDescription.classDescriptionForEntityName( entity );
 311  0
         if ( classDesc == null )
 312  
         {
 313  0
              throw new WotonomyException( "Unknown entity type: " + entity );   
 314  
         }
 315  
         
 316  0
         Object result = classDesc.createInstanceWithEditingContext( aContext, aGlobalID );
 317  0
         if ( result instanceof EOFaulting )
 318  
         {
 319  0
             ((EOFaulting)result).turnIntoFault( null );
 320  
         }
 321  0
         return result;
 322  
     }
 323  
     
 324  
     /**
 325  
     * Dumps the snapshot corresponding to the specified id.
 326  
     */
 327  
     protected void invalidateObject( EOGlobalID aGlobalID )
 328  
     {
 329  0
         snapshots.remove( aGlobalID );
 330  0
     }
 331  
      
 332  
     /**
 333  
     * Dumps all snapshots.
 334  
     */
 335  
     protected void invalidateAllCache()
 336  
     {
 337  0
         snapshots.clear();
 338  0
     }
 339  
      
 340  
     /**
 341  
     * Remove all values from all objects in memory, turning them into faults,
 342  
     * and posts a notification that all objects have been invalidated.
 343  
     */
 344  
     public void invalidateAllObjects()
 345  
     {
 346  0
         invalidateAllCache();
 347  
 
 348  
         // post notification
 349  0
         NSNotificationQueue.defaultQueue().enqueueNotification(
 350  0
             new NSNotification(
 351  0
                 InvalidatedAllObjectsInStoreNotification, this ),
 352  0
             NSNotificationQueue.PostNow );
 353  0
     }
 354  
 
 355  
     /**
 356  
     * Removes values with the specified ids from memory, turning them into
 357  
     * faults, and posts a notification that those objects have been invalidated.
 358  
     */
 359  
     public void invalidateObjectsWithGlobalIDs( List aList )
 360  
     {
 361  0
         NSArray empty = new NSArray();
 362  0
         NSMutableArray invalidated = new NSMutableArray();
 363  
 
 364  
         Object object;
 365  0
         Iterator iterator = aList.iterator();
 366  0
         while ( iterator.hasNext() )
 367  
         {
 368  0
             object = iterator.next();
 369  0
             invalidateObject( (EOGlobalID) object );
 370  0
             invalidated.addObject( object );
 371  0
         }
 372  
 
 373  0
         NSMutableDictionary info = new NSMutableDictionary();
 374  0
         info.setObjectForKey( empty, InsertedKey );
 375  0
         info.setObjectForKey( empty, UpdatedKey );
 376  0
         info.setObjectForKey( empty, DeletedKey );
 377  0
         info.setObjectForKey( invalidated, InvalidatedKey );
 378  
 
 379  
         // post notification
 380  0
         NSNotificationQueue.defaultQueue().
 381  0
             enqueueNotificationWithCoalesceMaskForModes( new NSNotification(
 382  0
                 ObjectsChangedInStoreNotification, this, info ),
 383  0
             NSNotificationQueue.PostNow,
 384  0
             NSNotificationQueue.NotificationNoCoalescing, null );
 385  0
     }
 386  
 
 387  
     /**
 388  
     * Returns false because locking is not currently permitted.
 389  
     */
 390  
     public boolean isObjectLockedWithGlobalID( EOGlobalID aGlobalID,
 391  
                                                EOEditingContext aContext )
 392  
     {
 393  0
         return false;
 394  
     }
 395  
 
 396  
     /**
 397  
     * Does nothing because locking is not currently permitted.
 398  
     */
 399  
     public void lockObjectWithGlobalID( EOGlobalID aGlobalID,
 400  
                                         EOEditingContext aContext )
 401  
     {
 402  
         // does nothing
 403  0
     }
 404  
 
 405  
     /**
 406  
     * Returns a List of objects associated with the object
 407  
     * with the specified id for the specified property
 408  
     * relationship.  This method may not return an array fault
 409  
     * because array faults call this method to fetch on demand.
 410  
     * All objects must be registered the specified editing context.
 411  
     * The specified relationship key must produce a result of
 412  
     * type Collection for the source object or an exception is thrown.
 413  
     */
 414  
     public NSArray objectsForSourceGlobalID( EOGlobalID aGlobalID,
 415  
                             String aRelationship, EOEditingContext aContext )
 416  
     { // System.out.println( "objectsForSourceGlobalID: " + aGlobalID + " : " + aRelationship + " : " ); 
 417  
 
 418  0
         Map snapshot = readFromCache( aGlobalID, new NSArray( aRelationship ) );
 419  0
         Object value = snapshot.get( aRelationship );
 420  0
         if ( value == null ) value = new NSArray(); // empty list
 421  0
         if ( ! ( value instanceof Collection ) )
 422  
         {
 423  0
             throw new RuntimeException( "Specified relationship is not a collection: " 
 424  0
                 + aRelationship + " : " + aGlobalID + " : " + value );
 425  
         }
 426  
         
 427  0
         NSArray result = new NSMutableArray();
 428  
 
 429  
         // get fault for each id
 430  
         EOGlobalID id;
 431  
         Object fault;
 432  0
         Iterator iterator = ((Collection)value).iterator();
 433  0
         while ( iterator.hasNext() )
 434  
         {
 435  0
             id = (EOGlobalID) iterator.next();
 436  
 
 437  
             // get registered fault
 438  0
             fault = aContext.faultForGlobalID( id, aContext );
 439  
 
 440  
             // assert fault
 441  0
             if ( fault == null )
 442  
             {
 443  
                 // this should never happen
 444  0
                 throw new RuntimeException(
 445  0
                     "Could not find fault for ID: " + id );
 446  
             }
 447  
 
 448  0
             result.add( fault );
 449  0
         }
 450  
 
 451  0
         fireObjectsChangedInStore();
 452  
 
 453  
 //System.out.println( "done" );
 454  0
         return result;
 455  
     }
 456  
     
 457  
     /**
 458  
     * Returns a List of objects the meet the criteria of
 459  
     * the supplied specification.  Faults are not allowed in the array.
 460  
     * Each object is registered with the specified editing context.
 461  
     * If any object is already fetched in the specified context,
 462  
     * it is not refetched and that object should be used in the array.
 463  
     */
 464  
     public NSArray objectsWithFetchSpecification(
 465  
                 EOFetchSpecification aFetchSpec, EOEditingContext aContext )
 466  
     {
 467  0
         NSMutableArray result = new NSMutableArray();
 468  
         
 469  
         //TODO: implement this
 470  
         
 471  0
         return result;
 472  
     }
 473  
 
 474  
     /**
 475  
     * Fires ObjectsChangedInStoreNotification
 476  
     * with contents of buffers and then clears buffers.
 477  
     * If buffers are empty, does nothing.
 478  
     */
 479  
     private void fireObjectsChangedInStore()
 480  
     {
 481  
         // check for changes to broadcast
 482  0
         if ( insertedIDsBuffer.size() + updatedIDsBuffer.size() +
 483  0
              deletedIDsBuffer.size() + invalidatedIDsBuffer.size() == 0 )
 484  
         {
 485  0
             return;
 486  
         }
 487  
 
 488  
         // broadcast ObjectsChangedInStoreNotification
 489  
         // for the benefit of child editing contexts
 490  
 
 491  0
         NSMutableDictionary storeInfo = new NSMutableDictionary();
 492  
 
 493  0
         storeInfo.setObjectForKey(
 494  0
             new NSArray( (Collection) insertedIDsBuffer ),
 495  0
             EOObjectStore.InsertedKey );
 496  0
         storeInfo.setObjectForKey(
 497  0
             new NSArray( (Collection) updatedIDsBuffer ),
 498  0
             EOObjectStore.UpdatedKey );
 499  0
         storeInfo.setObjectForKey(
 500  0
             new NSArray( (Collection) deletedIDsBuffer ),
 501  0
             EOObjectStore.DeletedKey );
 502  0
         storeInfo.setObjectForKey(
 503  0
             new NSArray( (Collection) invalidatedIDsBuffer ),
 504  0
             EOObjectStore.InvalidatedKey );
 505  
 
 506  
         // clear buffers
 507  
 
 508  0
         insertedIDsBuffer.removeAllObjects();
 509  0
         updatedIDsBuffer.removeAllObjects();
 510  0
         deletedIDsBuffer.removeAllObjects();
 511  0
         invalidatedIDsBuffer.removeAllObjects();
 512  
 
 513  
         // post notification
 514  0
         NSNotificationQueue.defaultQueue().
 515  0
             enqueueNotificationWithCoalesceMaskForModes( new NSNotification(
 516  0
                 ObjectsChangedInStoreNotification, this, storeInfo ),
 517  0
             NSNotificationQueue.PostNow,
 518  0
             NSNotificationQueue.NotificationNoCoalescing, null );
 519  0
     }
 520  
 
 521  
     /**
 522  
     * Removes all values from the specified object,
 523  
     * converting it into a fault for the specified id.
 524  
     * New or deleted objects should not be refaulted.
 525  
     */
 526  
     public void refaultObject( Object anObject, EOGlobalID aGlobalID,
 527  
                                EOEditingContext aContext )
 528  
     {
 529  
 //System.out.println( "refaultObject: " + aGlobalID ); 
 530  
 //new net.wotonomy.ui.swing.util.StackTraceInspector();
 531  0
         if ( anObject instanceof EOFaulting )
 532  
         {
 533  0
             ((EOFaulting)anObject).turnIntoFault( null );
 534  
         }
 535  0
     }
 536  
 
 537  
     /**
 538  
     * Writes all changes in the specified editing context
 539  
     * to the respository.
 540  
     */
 541  
     public void saveChangesInEditingContext ( EOEditingContext aContext )
 542  
     {
 543  
         Object result; // need a container result?
 544  
         Map updateMap;
 545  
         Object object;
 546  
         EOGlobalID id;
 547  
         Iterator iterator;
 548  
         
 549  
         //TODO: the ordering of operations here 
 550  
         // needs to be a lot more sophisticated.
 551  
     
 552  
         // process deletes first
 553  0
         iterator = aContext.deletedObjects().iterator();
 554  0
         while ( iterator.hasNext() )
 555  
         {
 556  0
             object = iterator.next();
 557  0
             id = aContext.globalIDForObject( object );
 558  
             try
 559  
             {
 560  0
                 result = deleteObject( id );
 561  
             }
 562  0
             catch ( Exception exc )
 563  
             {
 564  0
                 System.out.println( "Error deleting object: " + id );
 565  0
                 exc.printStackTrace();
 566  0
             }
 567  0
         }
 568  
         
 569  
         // process inserts next
 570  0
         iterator = aContext.insertedObjects().iterator();
 571  0
         while ( iterator.hasNext() )
 572  
         {
 573  0
             object = iterator.next();
 574  0
             processInsert( aContext, object );
 575  0
         }
 576  
         
 577  
         // process updates last
 578  0
         iterator = aContext.updatedObjects().iterator();
 579  0
         while ( iterator.hasNext() )
 580  
         {
 581  0
             object = iterator.next();
 582  0
             id = aContext.globalIDForObject( object );
 583  
             try
 584  
             {
 585  0
                 updateMap = getUpdateMap( aContext, object );
 586  0
                 result = updateObject( id, updateMap );
 587  
             }
 588  0
             catch ( Exception exc )
 589  
             {
 590  0
                 System.out.println( "Error updating object: " + id );
 591  0
                 exc.printStackTrace();
 592  0
             }
 593  0
         }
 594  
         
 595  
         //aContext.invalidateAllObjects();
 596  0
     }
 597  
     
 598  
     protected Object processInsert( EOEditingContext aContext, Object object )
 599  
     {
 600  0
         Map result = null; 
 601  0
         EOGlobalID id = aContext.globalIDForObject( object );
 602  
         try
 603  
         {
 604  
             Map updateMap;
 605  0
             updateMap = getUpdateMap( aContext, object ); 
 606  0
             result = insertObject( id, updateMap );
 607  0
             id = globalIDForData( result ); // read new permanent id
 608  
 
 609  
             // broadcast that the global id has changed.
 610  0
             NSMutableDictionary userInfo = new NSMutableDictionary();
 611  0
             userInfo.setObjectForKey( id, aContext.globalIDForObject( object ) );
 612  0
             NSNotificationQueue.defaultQueue().enqueueNotification(
 613  0
                 new NSNotification( EOGlobalID.GlobalIDChangedNotification,
 614  0
                 null , userInfo ), NSNotificationQueue.PostNow );
 615  
 
 616  
         }
 617  0
         catch ( Exception exc )
 618  
         {
 619  0
             System.out.println( "Error inserting object: " + id );
 620  0
             exc.printStackTrace();
 621  0
         }
 622  0
         return result;
 623  
     }
 624  
 
 625  
     /**
 626  
     * This method returns a map containing just the keys that are modified
 627  
     * for a given object, converting any to-one or to-many relationships
 628  
     * to id references.
 629  
     */
 630  
     protected Map getUpdateMap( EOEditingContext aContext, Object anObject )
 631  
     {
 632  0
         Map result = new HashMap();
 633  0
         EOEditingContext context = aContext;
 634  
         
 635  0
         String entity = entityForGlobalIDOrObject( null, anObject );
 636  0
         EOClassDescription classDesc = 
 637  0
             EOClassDescription.classDescriptionForEntityName( entity );
 638  0
         if ( classDesc == null )
 639  
         {
 640  0
             throw new WotonomyException( "Unknown entity type: " + entity );   
 641  
         }
 642  
         
 643  0
         NSArray oneKeys = classDesc.toOneRelationshipKeys();
 644  0
         NSArray manyKeys = classDesc.toManyRelationshipKeys();
 645  
         
 646  
         String key;
 647  
         Object value;
 648  
         EOGlobalID id;
 649  
 
 650  0
         Collection changedKeys = changedKeysForObject( anObject );
 651  0
         if ( changedKeys == null ) 
 652  
         {
 653  
             // assume all keys changed
 654  0
             changedKeys = classDesc.attributeKeys();
 655  0
             changedKeys.addAll( oneKeys );
 656  0
             changedKeys.addAll( manyKeys );
 657  
         }
 658  0
         Iterator iterator = changedKeys.iterator();
 659  0
         while ( iterator.hasNext() )
 660  
         {
 661  0
             key = iterator.next().toString();
 662  0
             if ( anObject instanceof EOKeyValueCoding )
 663  
             {
 664  0
                 value = ((EOKeyValueCoding)anObject).storedValueForKey( key );
 665  0
             }
 666  
             else
 667  
             {
 668  0
                 value = EOKeyValueCodingSupport.storedValueForKey( anObject, key );
 669  
             }
 670  
             
 671  
             // convert to-one relationship to oid
 672  0
             if ( oneKeys.contains( key ) )
 673  
             {
 674  0
                 id = context.globalIDForObject( value );
 675  
                 
 676  
                 // if this id hasn't been persisted, save it first
 677  
                 // NOTE: this won't work for self-referential graphs of objects!
 678  0
                 if ( id.isTemporary() )
 679  
                 {
 680  0
                     processInsert( aContext, value );   
 681  0
                     id = context.globalIDForObject( value );
 682  
                 }
 683  
                 
 684  0
                 value = id;
 685  0
             }
 686  
             else
 687  
             // convert to-many relationship list to oid list
 688  0
             if ( manyKeys.contains( key ) )
 689  
             {
 690  
                 //NOTE: we can assume that array faults that 
 691  
                 // are marked as changed have been fired.
 692  0
                 if ( value instanceof Collection )
 693  
                 {
 694  
                     Object object;
 695  0
                     Collection newValue = new LinkedList();
 696  0
                     Iterator jiterator = ((Collection)value).iterator();
 697  0
                     while ( jiterator.hasNext() )
 698  
                     {
 699  0
                         object = jiterator.next();
 700  0
                         id = context.globalIDForObject( object );
 701  
 
 702  
                         // if this id hasn't been persisted, save it first
 703  
                         // NOTE: this won't work for self-referential graphs of objects!
 704  0
                         if ( id.isTemporary() )
 705  
                         {
 706  0
                             processInsert( aContext, object );   
 707  0
                             id = context.globalIDForObject( object );
 708  
                         }
 709  0
                         newValue.add( id );
 710  0
                     }
 711  0
                     value = newValue;
 712  0
                 }
 713  
                 else
 714  
                 {
 715  
                     // should never happen
 716  0
                     new RuntimeException( 
 717  0
                         "Can't update to-many relationship because it's not a Collection." )
 718  0
                         .printStackTrace();
 719  
                 }
 720  
             }
 721  
             
 722  
             // place value in map 
 723  0
             result.put( key, value );
 724  0
         }
 725  
         
 726  0
 System.out.println( result );
 727  0
         return result;
 728  
     }
 729  
 
 730  
 /*
 731  
  * $Log$
 732  
  * Revision 1.2  2006/02/16 16:47:14  cgruber
 733  
  * Move some classes in to "internal" packages and re-work imports, etc.
 734  
  *
 735  
  * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions.
 736  
  *
 737  
  * Revision 1.1  2006/02/16 13:19:57  cgruber
 738  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 739  
  *
 740  
  * Revision 1.5  2003/12/18 15:37:38  mpowers
 741  
  * Changes to retain ability to work with objects that don't necessarily
 742  
  * implement EOEnterpriseObject.  I would still like to preserve this case
 743  
  * for general usage, however the access package is free to assume that
 744  
  * those objects will be EOs and cast appropriately.
 745  
  *
 746  
  * Revision 1.4  2003/08/19 01:53:12  chochos
 747  
  * 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.
 748  
  *
 749  
  * Revision 1.3  2002/10/24 18:18:12  mpowers
 750  
  * NSArray's are now considered read-only, so we can return our internal
 751  
  * representation to reduce unnecessary object allocation.
 752  
  *
 753  
  * Revision 1.2  2002/01/19 17:27:49  mpowers
 754  
  * Implemented most of it.
 755  
  *
 756  
  * Revision 1.1  2001/11/25 22:44:02  mpowers
 757  
  * Contributing draft of AbstractObjectStore.
 758  
  *
 759  
  * 
 760  
  */
 761  
 }
 762