| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
| AbstractObjectStore |
|
| 2.5714285714285716;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 |