KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > access > ObjectStore


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.access;
57
58 import java.io.Serializable JavaDoc;
59 import java.util.ArrayList JavaDoc;
60 import java.util.Collection JavaDoc;
61 import java.util.Collections JavaDoc;
62 import java.util.HashMap JavaDoc;
63 import java.util.Iterator JavaDoc;
64 import java.util.List JavaDoc;
65 import java.util.Map JavaDoc;
66
67 import org.apache.log4j.Logger;
68 import org.objectstyle.cayenne.CayenneRuntimeException;
69 import org.objectstyle.cayenne.DataObject;
70 import org.objectstyle.cayenne.DataRow;
71 import org.objectstyle.cayenne.Fault;
72 import org.objectstyle.cayenne.ObjectId;
73 import org.objectstyle.cayenne.PersistenceState;
74 import org.objectstyle.cayenne.access.event.SnapshotEvent;
75 import org.objectstyle.cayenne.access.event.SnapshotEventListener;
76 import org.objectstyle.cayenne.event.EventManager;
77 import org.objectstyle.cayenne.map.ObjEntity;
78 import org.objectstyle.cayenne.map.ObjRelationship;
79 import org.objectstyle.cayenne.util.Util;
80 import org.objectstyle.cayenne.validation.ValidationException;
81 import org.objectstyle.cayenne.validation.ValidationResult;
82
83 /**
84  * ObjectStore maintains a cache of objects and their snapshots.
85  * <p>
86  * <strong>Synchronization Note: </strong> Since there is often a need to synchronize on
87  * both, ObjectStore and underlying DataRowCache, there must be a consistent
88  * synchronization policy to avoid deadlocks. Whenever ObjectStore needs to obtain a lock
89  * on DataRowStore, it must obtain a lock on self.
90  * </p>
91  *
92  * @author Andrei Adamchik
93  */

94 public class ObjectStore implements Serializable JavaDoc, SnapshotEventListener {
95
96     private static Logger logObj = Logger.getLogger(ObjectStore.class);
97
98     protected transient Map JavaDoc newObjectMap = null;
99
100     protected Map JavaDoc objectMap = new HashMap JavaDoc();
101     protected Map JavaDoc queryResultMap = new HashMap JavaDoc();
102
103     // TODO: we may implement more fine grained tracking of related objects
104
// changes, requiring more sophisticated data structure to hold them
105
protected List JavaDoc indirectlyModifiedIds = new ArrayList JavaDoc();
106
107     protected List JavaDoc flattenedInserts = new ArrayList JavaDoc();
108     protected List JavaDoc flattenedDeletes = new ArrayList JavaDoc();
109
110     /**
111      * Ensures access to the versions of DataObject snapshots (in the form of DataRows)
112      * taken when an object was first modified.
113      */

114     protected Map JavaDoc retainedSnapshotMap = new HashMap JavaDoc();
115
116     /**
117      * Stores a reference to the DataRowStore.
118      * <p>
119      * <i>Serialization note: </i> It is up to the owner of this ObjectStore to initialize
120      * DataRowStore after deserialization of this object. ObjectStore will not know how to
121      * restore the DataRowStore by itself.
122      * </p>
123      */

124     protected transient DataRowStore dataRowCache;
125
126     public ObjectStore() {
127     }
128
129     public ObjectStore(DataRowStore dataRowCache) {
130         this();
131         setDataRowCache(dataRowCache);
132     }
133     
134     /**
135      * Returns a number of objects currently registered with this ObjectStore.
136      *
137      * @since 1.2
138      */

139     public int registeredObjectsCount() {
140         return objectMap.size();
141     }
142     
143     /**
144      * Returns a number of query results cached by this object store. Note that each
145      * result is a list and can possibly contain a large number of entries.
146      *
147      * @since 1.2
148      */

149     public int cachedQueriesCount() {
150         return queryResultMap.size();
151     }
152
153     /**
154      * Saves a committed snapshot for an object in a non-expiring cache. This ensures that
155      * Cayenne can track object changes even if the underlying cache entry has expired or
156      * replaced with a newer version. Retained snapshots are evicted when an object is
157      * committed or rolled back.
158      * <p>
159      * When committing modified objects, comparing them with retained snapshots instead of
160      * the currently cached snapshots would allow to resolve certain conflicts during
161      * concurrent modification of <strong>different attributes </strong> of the same
162      * objects by different DataContexts.
163      * </p>
164      *
165      * @since 1.1
166      */

167     public synchronized void retainSnapshot(DataObject object) {
168         ObjectId oid = object.getObjectId();
169         DataRow snapshot = getCachedSnapshot(oid);
170
171         if (snapshot == null) {
172             snapshot = object.getDataContext().currentSnapshot(object);
173         }
174         // if a snapshot has changed underneath, try a merge...
175
else if (snapshot.getVersion() != object.getSnapshotVersion()) {
176             DataContextDelegate delegate = object.getDataContext().nonNullDelegate();
177             if (delegate.shouldMergeChanges(object, snapshot)) {
178                 ObjEntity entity = object
179                         .getDataContext()
180                         .getEntityResolver()
181                         .lookupObjEntity(object);
182                 DataRowUtils.forceMergeWithSnapshot(entity, object, snapshot);
183                 object.setSnapshotVersion(snapshot.getVersion());
184                 delegate.finishedMergeChanges(object);
185             }
186         }
187
188         retainSnapshot(object, snapshot);
189     }
190
191     /**
192      * Stores provided DataRow as a snapshot to be used to build UPDATE queries for an
193      * object. Updates object's snapshot version with the version of the new retained
194      * snapshot.
195      *
196      * @since 1.1
197      */

198     protected synchronized void retainSnapshot(DataObject object, DataRow snapshot) {
199         this.retainedSnapshotMap.put(object.getObjectId(), snapshot);
200     }
201
202     /**
203      * Returns a DataRowStore associated with this ObjectStore.
204      */

205     public DataRowStore getDataRowCache() {
206         return dataRowCache;
207     }
208
209     /**
210      * Sets parent SnapshotCache. Registers to receive SnapshotEvents if the cache is
211      * configured to allow ObjectStores to receive such events.
212      */

213     public void setDataRowCache(DataRowStore dataRowCache) {
214         if (dataRowCache == this.dataRowCache) {
215             return;
216         }
217
218         // IMPORTANT: listen for all senders on a given EventSubject,
219
// filtering of events will be done in the handler method.
220

221         if (this.dataRowCache != null) {
222             EventManager.getDefaultManager().removeListener(
223                     this,
224                     this.dataRowCache.getSnapshotEventSubject());
225         }
226
227         this.dataRowCache = dataRowCache;
228
229         if (dataRowCache != null) {
230             // setting itself as non-blocking listener,
231
// since event sending thread will likely be locking sender's
232
// ObjectStore and snapshot cache itself.
233
EventManager.getDefaultManager().addNonBlockingListener(
234                     this,
235                     "snapshotsChanged",
236                     SnapshotEvent.class,
237                     dataRowCache.getSnapshotEventSubject());
238         }
239     }
240
241     /**
242      * Invalidates a collection of DataObjects. Changes objects state to HOLLOW.
243      */

244     public synchronized void objectsInvalidated(Collection JavaDoc objects) {
245         if (objects.isEmpty()) {
246             return;
247         }
248
249         Collection JavaDoc ids = new ArrayList JavaDoc(objects.size());
250         Iterator JavaDoc it = objects.iterator();
251         while (it.hasNext()) {
252             DataObject object = (DataObject) it.next();
253
254             // we don't care about NEW objects,
255
// but we still do care about HOLLOW, since snapshot might still be
256
// present
257
if (object.getPersistenceState() == PersistenceState.NEW) {
258                 continue;
259             }
260
261             object.setPersistenceState(PersistenceState.HOLLOW);
262
263             // remove snapshot, but keep the object
264
dataRowCache.forgetSnapshot(object.getObjectId());
265
266             // remove cached changes
267
indirectlyModifiedIds.remove(object.getObjectId());
268
269             // remember the id
270
ids.add(object.getObjectId());
271         }
272
273         // send an event for removed snapshots
274
getDataRowCache().processSnapshotChanges(
275                 this,
276                 Collections.EMPTY_MAP,
277                 Collections.EMPTY_LIST,
278                 ids,
279                 Collections.EMPTY_LIST);
280     }
281
282     /**
283      * Evicts a collection of DataObjects from the ObjectStore. Object snapshots are
284      * removed as well. Changes objects state to TRANSIENT. This method can be used for
285      * manual cleanup of Cayenne cache.
286      */

287     public synchronized void objectsUnregistered(Collection JavaDoc objects) {
288         if (objects.isEmpty()) {
289             return;
290         }
291
292         Iterator JavaDoc it = objects.iterator();
293         while (it.hasNext()) {
294             DataObject object = (DataObject) it.next();
295
296             // remove object but not snapshot
297
objectMap.remove(object.getObjectId());
298             indirectlyModifiedIds.remove(object.getObjectId());
299             dataRowCache.forgetSnapshot(object.getObjectId());
300
301             object.setDataContext(null);
302             object.setObjectId(null);
303             object.setPersistenceState(PersistenceState.TRANSIENT);
304         }
305
306         // no snapshot events needed... snapshots maybe cleared, but no
307
// database changes have occured.
308
}
309
310     /**
311      * Reverts changes to all stored uncomitted objects.
312      *
313      * @since 1.1
314      */

315     public synchronized void objectsRolledBack() {
316         Iterator JavaDoc it = getObjectIterator();
317
318         // collect candidates
319
while (it.hasNext()) {
320             DataObject object = (DataObject) it.next();
321             int objectState = object.getPersistenceState();
322             switch (objectState) {
323                 case PersistenceState.NEW:
324                     it.remove();
325
326                     object.setDataContext(null);
327                     object.setObjectId(null);
328                     object.setPersistenceState(PersistenceState.TRANSIENT);
329                     break;
330                 case PersistenceState.DELETED:
331                 // Do the same as for modified... deleted is only a persistence state, so
332
// rolling the object back will set the state to committed
333
case PersistenceState.MODIFIED:
334                     // this will clean any modifications and defer refresh from snapshot
335
// till the next object accessor is called
336
object.setPersistenceState(PersistenceState.HOLLOW);
337                     break;
338                 default:
339                     //Transient, committed and hollow need no handling
340
break;
341             }
342         }
343
344         // clear caches
345
// TODO: the same operation is performed on commit... must create a common method
346
this.retainedSnapshotMap.clear();
347         this.indirectlyModifiedIds.clear();
348         this.flattenedDeletes.clear();
349         this.flattenedInserts.clear();
350     }
351
352     /**
353      * Performs tracking of object relationship changes.
354      *
355      * @since 1.1
356      */

357     public void objectRelationshipUnset(
358             DataObject source,
359             DataObject target,
360             ObjRelationship relationship,
361             boolean processFlattened) {
362
363         objectRelationshipChanged(source, relationship);
364
365         if (processFlattened) {
366             flattenedRelationshipUnset(source, relationship, target);
367         }
368     }
369
370     /**
371      * Performs tracking of object relationship changes.
372      *
373      * @since 1.1
374      */

375     public void objectRelationshipSet(
376             DataObject source,
377             DataObject target,
378             ObjRelationship relationship,
379             boolean processFlattened) {
380
381         objectRelationshipChanged(source, relationship);
382
383         if (processFlattened) {
384             flattenedRelationshipSet(source, relationship, target);
385         }
386     }
387
388     /**
389      * Performs tracking of object relationship changes.
390      *
391      * @since 1.1
392      */

393     void objectRelationshipChanged(DataObject object, ObjRelationship relationship) {
394         // track modifications to an "independent" relationship
395
if (relationship.isSourceIndependentFromTargetChange()) {
396             int state = object.getPersistenceState();
397             if (state == PersistenceState.COMMITTED
398                     || state == PersistenceState.HOLLOW
399                     || state == PersistenceState.MODIFIED) {
400
401                 synchronized (this) {
402                     indirectlyModifiedIds.add(object.getObjectId());
403                 }
404             }
405         }
406     }
407
408     /**
409      * Updates underlying DataRowStore. If <code>refresh</code> is true, all snapshots
410      * in <code>snapshots</code> will be loaded into DataRowStore, regardless of the
411      * existing cache state. If <code>refresh</code> is false, only missing snapshots
412      * are loaded. This method is normally called by Cayenne internally to synchronized
413      * snapshots of recently fetched objects.
414      *
415      * @param objects a list of object whose snapshots need to be updated in the
416      * SnapshotCache
417      * @param snapshots a list of snapshots. Must be the same size and use the same order
418      * as <code>objects</code> list.
419      * @param refresh controls whether existing cached snapshots should be replaced with
420      * the new ones.
421      * @since 1.1
422      */

423     public void snapshotsUpdatedForObjects(List JavaDoc objects, List JavaDoc snapshots, boolean refresh) {
424
425         // sanity check
426
if (objects.size() != snapshots.size()) {
427             throw new IllegalArgumentException JavaDoc(
428                     "Counts of objects and corresponding snapshots do not match. "
429                             + "Objects count: "
430                             + objects.size()
431                             + ", snapshots count: "
432                             + snapshots.size());
433         }
434
435         Map JavaDoc modified = null;
436
437         synchronized (this) {
438             int size = objects.size();
439             for (int i = 0; i < size; i++) {
440                 DataObject object = (DataObject) objects.get(i);
441                 ObjectId oid = object.getObjectId();
442
443                 // add snapshots if refresh is forced, or if a snapshot is
444
// missing
445
DataRow cachedSnapshot = getCachedSnapshot(oid);
446                 if (refresh || cachedSnapshot == null) {
447
448                     DataRow newSnapshot = (DataRow) snapshots.get(i);
449
450                     if (cachedSnapshot != null) {
451                         // use old snapshot if no changes occurred
452
if (cachedSnapshot.equals(newSnapshot)) {
453                             object.setSnapshotVersion(cachedSnapshot.getVersion());
454                             continue;
455                         }
456                         else {
457                             newSnapshot.setReplacesVersion(cachedSnapshot.getVersion());
458                         }
459                     }
460
461                     if (modified == null) {
462                         modified = new HashMap JavaDoc();
463                     }
464
465                     modified.put(oid, newSnapshot);
466                 }
467             }
468
469             if (modified != null) {
470                 getDataRowCache().processSnapshotChanges(
471                         this,
472                         modified,
473                         Collections.EMPTY_LIST,
474                         Collections.EMPTY_LIST,
475                         Collections.EMPTY_LIST);
476             }
477         }
478     }
479
480     /**
481      * Processes internal objects after the parent DataContext was committed. Changes
482      * object persistence state and handles snapshot updates.
483      *
484      * @since 1.1
485      */

486     public synchronized void objectsCommitted() {
487         // these will store snapshot changes
488
List JavaDoc deletedIds = null;
489         Map JavaDoc modifiedSnapshots = null;
490
491         Iterator JavaDoc entries = objectMap.entrySet().iterator();
492         List JavaDoc modifiedIds = null;
493
494         while (entries.hasNext()) {
495             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) entries.next();
496             
497             DataObject object = (DataObject) entry.getValue();
498             int state = object.getPersistenceState();
499             ObjectId id = object.getObjectId();
500             
501             // OID may have been manually substituted instead of using replacement...
502
// not good, but process it here anyway...
503
// [an alternative would be to throw an exception, but as commit is already
504
// done, this is not a sensible thing to do]
505
if(state == PersistenceState.NEW && !id.isReplacementIdAttached()) {
506                 id = fixObjectId((ObjectId) entry.getKey(), object);
507             }
508
509             if (id.isReplacementIdAttached()) {
510                 if (modifiedIds == null) {
511                     modifiedIds = new ArrayList JavaDoc();
512                 }
513
514                 modifiedIds.add(id);
515
516                 // postpone processing of objects that require an id change
517
continue;
518             }
519             // sanity check
520
else if (id.isTemporary()) {
521                 throw new CayenneRuntimeException(
522                         "Temporary ID hasn't been replaced on commit: " + object);
523             }
524
525             // inserted will all have replacement ids, so do not check for
526
// inserts here
527
// ...
528

529             // deleted
530
switch (state) {
531                 case PersistenceState.DELETED:
532                     entries.remove();
533                     dataRowCache.forgetSnapshot(id);
534                     object.setDataContext(null);
535                     object.setPersistenceState(PersistenceState.TRANSIENT);
536
537                     if (deletedIds == null) {
538                         deletedIds = new ArrayList JavaDoc();
539                     }
540
541                     deletedIds.add(id);
542                     break;
543                 // modified
544
case PersistenceState.MODIFIED:
545                     if (modifiedSnapshots == null) {
546                         modifiedSnapshots = new HashMap JavaDoc();
547                     }
548
549                     DataRow dataRow = object.getDataContext().currentSnapshot(object);
550
551                     modifiedSnapshots.put(id, dataRow);
552                     dataRow.setReplacesVersion(object.getSnapshotVersion());
553
554                     object.setPersistenceState(PersistenceState.COMMITTED);
555                     object.setSnapshotVersion(dataRow.getVersion());
556                     break;
557                 // new but without a replacement ID (users may have crafted a perm id
558
// manually)
559
case PersistenceState.NEW:
560                     // TODO: do we need to fix snapshots around?
561
object.setPersistenceState(PersistenceState.COMMITTED);
562                     break;
563             }
564         }
565
566         // process id replacements
567
if (modifiedIds != null) {
568             Iterator JavaDoc ids = modifiedIds.iterator();
569             while (ids.hasNext()) {
570                 ObjectId id = (ObjectId) ids.next();
571                 DataObject object = getObject(id);
572
573                 if (object == null) {
574                     throw new CayenneRuntimeException("No object for id: " + id);
575                 }
576
577                 // store old snapshot as deleted,
578
// even though the object was modified, not deleted
579
// from the common logic standpoint..
580
if (!id.isTemporary()) {
581                     if (deletedIds == null) {
582                         deletedIds = new ArrayList JavaDoc();
583                     }
584
585                     deletedIds.add(id);
586                 }
587
588                 // store the new snapshot
589
if (modifiedSnapshots == null) {
590                     modifiedSnapshots = new HashMap JavaDoc();
591                 }
592
593                 ObjectId replacementId = id.createReplacementId();
594                 DataRow dataRow = object.getDataContext().currentSnapshot(object);
595                 modifiedSnapshots.put(replacementId, dataRow);
596                 dataRow.setReplacesVersion(object.getSnapshotVersion());
597
598                 // fix object state
599
object.setObjectId(replacementId);
600                 object.setSnapshotVersion(dataRow.getVersion());
601                 object.setPersistenceState(PersistenceState.COMMITTED);
602                 addObject(object);
603
604                 objectMap.remove(id);
605                 dataRowCache.forgetSnapshot(id);
606             }
607         }
608
609         // notify parent cache
610
if (deletedIds != null || modifiedSnapshots != null) {
611             getDataRowCache()
612                     .processSnapshotChanges(
613                             this,
614                             modifiedSnapshots != null
615                                     ? modifiedSnapshots
616                                     : Collections.EMPTY_MAP,
617                             deletedIds != null ? deletedIds : Collections.EMPTY_LIST,
618                             Collections.EMPTY_LIST,
619                             !indirectlyModifiedIds.isEmpty() ? new ArrayList JavaDoc(
620                                     indirectlyModifiedIds) : Collections.EMPTY_LIST);
621         }
622
623         // clear caches
624
this.retainedSnapshotMap.clear();
625         this.indirectlyModifiedIds.clear();
626         this.flattenedDeletes.clear();
627         this.flattenedInserts.clear();
628     }
629     
630     
631     /**
632      * A hack to fix manual ObjectId replacements that may have been done without regards
633      * to the fact that ObjectId is used as a key in ObjectStore. E.g.
634      * http://objectstyle.org/cayenne/lists/cayenne-user/2005/01/0210.html. Still not sure
635      * if this is a sensible thing to do, but we can't leave this condition unhandled either.
636      *
637      * @since 1.2
638      */

639     ObjectId fixObjectId(ObjectId registeredId, DataObject object) {
640         if (!registeredId.equals(object.getObjectId())
641                 && !object.getObjectId().isTemporary()) {
642
643             registeredId.getReplacementIdMap().putAll(
644                     object.getObjectId().getIdSnapshot());
645
646             object.setObjectId(registeredId);
647         }
648
649         return registeredId;
650     }
651
652     public synchronized void addObject(DataObject obj) {
653         objectMap.put(obj.getObjectId(), obj);
654
655         if (newObjectMap != null) {
656             newObjectMap.put(obj.getObjectId(), obj);
657         }
658     }
659
660     /**
661      * Starts tracking the registration of new objects from this ObjectStore. Used in
662      * conjunction with unregisterNewObjects() to control garbage collection when an
663      * instance of ObjectStore is used over a longer time for batch processing.
664      * (TODO: this won't work with changeObjectKey()?)
665      *
666      * @see org.objectstyle.cayenne.access.ObjectStore#unregisterNewObjects()
667      */

668     public synchronized void startTrackingNewObjects() {
669         // TODO: something like shared DataContext or nested DataContext
670
// would hopefully obsolete this feature...
671
newObjectMap = new HashMap JavaDoc();
672     }
673
674     /**
675      * Unregisters the newly registered DataObjects from this objectStore. Used in
676      * conjunction with startTrackingNewObjects() to control garbage collection when an
677      * instance of ObjectStore is used over a longer time for batch processing.
678      * (TODO: this won't work with changeObjectKey()?)
679      *
680      * @see org.objectstyle.cayenne.access.ObjectStore#startTrackingNewObjects()
681      */

682     public synchronized void unregisterNewObjects() {
683         // TODO: something like shared DataContext or nested DataContext
684
// would hopefully obsolete this feature...
685

686         if(newObjectMap == null) {
687             return;
688         }
689
690         Iterator JavaDoc it = newObjectMap.values().iterator();
691
692         while (it.hasNext()) {
693             DataObject dataObj = (DataObject) it.next();
694
695             ObjectId oid = dataObj.getObjectId();
696             objectMap.remove(oid);
697             dataRowCache.forgetSnapshot(oid);
698
699             dataObj.setDataContext(null);
700             dataObj.setObjectId(null);
701             dataObj.setPersistenceState(PersistenceState.TRANSIENT);
702         }
703         newObjectMap.clear();
704         newObjectMap = null;
705     }
706
707     /**
708      * Returns a DataObject registered for a given ObjectId, or null if no such object
709      * exists. This method does not do a database fetch.
710      */

711     public synchronized DataObject getObject(ObjectId id) {
712         return (DataObject) objectMap.get(id);
713     }
714
715     public synchronized DataRow getRetainedSnapshot(ObjectId oid) {
716         return (DataRow) retainedSnapshotMap.get(oid);
717     }
718
719     /**
720      * Returns a snapshot for ObjectId from the underlying snapshot cache. If cache
721      * contains no snapshot, a null is returned.
722      *
723      * @since 1.1
724      */

725     public DataRow getCachedSnapshot(ObjectId oid) {
726         DataRow retained = getRetainedSnapshot(oid);
727         return (retained != null) ? retained : getDataRowCache().getCachedSnapshot(oid);
728     }
729
730     /**
731      * Returns cached query results for a given query, or null if no results are cached.
732      * Note that ObjectStore will only lookup results in its local cache, and not the
733      * shared cache associated with the underlying DataRowStore.
734      *
735      * @since 1.1
736      */

737     public synchronized List JavaDoc getCachedQueryResult(String JavaDoc name) {
738         // results should have been stored as rows or objects when
739
// they were originally cached... do no conversions here
740
return (List JavaDoc) queryResultMap.get(name);
741     }
742
743     /**
744      * Caches a list of query results.
745      *
746      * @since 1.1
747      */

748     public synchronized void cacheQueryResult(String JavaDoc name, List JavaDoc results) {
749         queryResultMap.put(name, results);
750     }
751
752     /**
753      * Returns a snapshot for ObjectId from the underlying snapshot cache. If cache
754      * contains no snapshot, it will attempt fetching it using provided QueryEngine. If
755      * fetch attempt fails or inconsistent data is returned, underlying cache will throw a
756      * CayenneRuntimeException.
757      *
758      * @since 1.1
759      */

760     public synchronized DataRow getSnapshot(ObjectId oid, QueryEngine engine) {
761         DataRow retained = getRetainedSnapshot(oid);
762         return (retained != null) ? retained : getDataRowCache().getSnapshot(oid, engine);
763     }
764
765     /**
766      * Returns a list of objects that are registered with this DataContext, regardless of
767      * their persistence state. List is returned by copy and can be modified by the
768      * caller.
769      */

770     public synchronized List JavaDoc getObjects() {
771         return new ArrayList JavaDoc(objectMap.values());
772     }
773
774     /**
775      * Returns an iterator over the registered objects.
776      */

777     public synchronized Iterator JavaDoc getObjectIterator() {
778         return objectMap.values().iterator();
779     }
780
781     /**
782      * Returns <code>true</code> if there are any modified, deleted or new objects
783      * registered with this ObjectStore, <code>false</code> otherwise.
784      */

785     public synchronized boolean hasChanges() {
786
787         // TODO: This implementation is rather naive and would scan all
788
// registered
789
// objects. Any better ideas? Catching events or something...
790

791         if (!flattenedInserts.isEmpty() || !flattenedDeletes.isEmpty()) {
792             return true;
793         }
794
795         Iterator JavaDoc it = getObjectIterator();
796         while (it.hasNext()) {
797             DataObject dataObject = (DataObject) it.next();
798             int state = dataObject.getPersistenceState();
799
800             if (state == PersistenceState.MODIFIED) {
801                 DataContext context = dataObject.getDataContext();
802                 DataRow committedSnapshot = getSnapshot(dataObject.getObjectId(), context);
803                 if (committedSnapshot == null) {
804                     return true;
805                 }
806
807                 DataRow currentSnapshot = context.currentSnapshot(dataObject);
808
809                 Iterator JavaDoc currentIt = currentSnapshot.entrySet().iterator();
810                 while (currentIt.hasNext()) {
811                     Map.Entry JavaDoc entry = (Map.Entry JavaDoc) currentIt.next();
812                     Object JavaDoc newValue = entry.getValue();
813                     Object JavaDoc oldValue = committedSnapshot.get(entry.getKey());
814                     if (!Util.nullSafeEquals(oldValue, newValue)) {
815                         return true;
816                     }
817                 }
818
819                 // original snapshot can have extra keys that are missing in the
820
// current snapshot; process those
821
Iterator JavaDoc committedIt = committedSnapshot.entrySet().iterator();
822                 while (committedIt.hasNext()) {
823
824                     Map.Entry JavaDoc entry = (Map.Entry JavaDoc) committedIt.next();
825
826                     // committed snapshot has null value, skip it
827
if (entry.getValue() == null) {
828                         continue;
829                     }
830
831                     if (!currentSnapshot.containsKey(entry.getKey())) {
832                         return true;
833                     }
834                 }
835             }
836             else if (state == PersistenceState.NEW || state == PersistenceState.DELETED) {
837                 return true;
838             }
839         }
840         return false;
841     }
842
843     /**
844      * Return a subset of registered objects that are in a certian persistence state.
845      * Collection is returned by copy.
846      */

847     public synchronized List JavaDoc objectsInState(int state) {
848         List JavaDoc filteredObjects = new ArrayList JavaDoc();
849
850         Iterator JavaDoc it = objectMap.values().iterator();
851         while (it.hasNext()) {
852             DataObject nextObj = (DataObject) it.next();
853             if (nextObj.getPersistenceState() == state)
854                 filteredObjects.add(nextObj);
855         }
856
857         return filteredObjects;
858     }
859
860     /**
861      * SnapshotEventListener implementation that processes snapshot change event, updating
862      * DataObjects that have the changes.
863      * <p>
864      * <i>Implementation note: </i> This method should not attempt to alter the underlying
865      * DataRowStore, since it is normally invoked *AFTER* the DataRowStore was modified as
866      * a result of some external interaction.
867      * </p>
868      *
869      * @since 1.1
870      */

871     public void snapshotsChanged(SnapshotEvent event) {
872         // filter events that we should not process
873
if (event.getPostedBy() != this.dataRowCache || event.getSource() == this) {
874             return;
875         }
876
877         // merge objects with changes in event...
878
if (logObj.isDebugEnabled()) {
879             logObj.debug("Received: " + event);
880         }
881
882         synchronized (this) {
883             processUpdatedSnapshots(event.getModifiedDiffs());
884             processDeletedIDs(event.getDeletedIds());
885             processInvalidatedIDs(event.getInvalidatedIds());
886             processIndirectlyModifiedIDs(event.getIndirectlyModifiedIds());
887         }
888     }
889
890     /**
891      * Performs validation of all uncommitted objects in the ObjectStore. If validation
892      * fails, a ValidationException is thrown, listing all encountered failures.
893      *
894      * @since 1.1
895      * @throws ValidationException
896      */

897     public synchronized void validateUncommittedObjects() throws ValidationException {
898
899         // we must iterate over a copy of object list,
900
// as calling validateFor* on DataObjects can have a side effect
901
// of modifying this ObjectStore, and thus resulting in
902
// ConcurrentModificationExceptions in the Iterator
903

904         Collection JavaDoc deleted = null;
905         Collection JavaDoc inserted = null;
906         Collection JavaDoc updated = null;
907
908         Iterator JavaDoc allIt = getObjectIterator();
909         while (allIt.hasNext()) {
910             DataObject dataObject = (DataObject) allIt.next();
911             switch (dataObject.getPersistenceState()) {
912                 case PersistenceState.NEW:
913                     if (inserted == null) {
914                         inserted = new ArrayList JavaDoc();
915                     }
916                     inserted.add(dataObject);
917                     break;
918                 case PersistenceState.MODIFIED:
919                     if (updated == null) {
920                         updated = new ArrayList JavaDoc();
921                     }
922                     updated.add(dataObject);
923                     break;
924                 case PersistenceState.DELETED:
925                     if (deleted == null) {
926                         deleted = new ArrayList JavaDoc();
927                     }
928                     deleted.add(dataObject);
929                     break;
930             }
931         }
932
933         ValidationResult validationResult = new ValidationResult();
934
935         if (deleted != null) {
936             Iterator JavaDoc it = deleted.iterator();
937             while (it.hasNext()) {
938                 DataObject dataObject = (DataObject) it.next();
939                 dataObject.validateForDelete(validationResult);
940             }
941         }
942
943         if (inserted != null) {
944             Iterator JavaDoc it = inserted.iterator();
945             while (it.hasNext()) {
946                 DataObject dataObject = (DataObject) it.next();
947                 dataObject.validateForInsert(validationResult);
948             }
949         }
950
951         if (updated != null) {
952             Iterator JavaDoc it = updated.iterator();
953             while (it.hasNext()) {
954                 DataObject dataObject = (DataObject) it.next();
955                 dataObject.validateForUpdate(validationResult);
956             }
957         }
958
959         if (validationResult.hasFailures()) {
960             throw new ValidationException(validationResult);
961         }
962     }
963
964     /**
965      * Initializes object with data from cache or from the database, if this object is not
966      * fully resolved.
967      *
968      * @since 1.1
969      */

970     public void resolveHollow(DataObject object) {
971         if (object.getPersistenceState() != PersistenceState.HOLLOW) {
972             return;
973         }
974
975         // no way to resolve faults outside of DataContext.
976
DataContext context = object.getDataContext();
977         if (context == null) {
978             object.setPersistenceState(PersistenceState.TRANSIENT);
979             return;
980         }
981
982         synchronized (this) {
983             DataRow snapshot = getSnapshot(object.getObjectId(), context);
984
985             // handle deleted object
986
if (snapshot == null) {
987                 processDeletedIDs(Collections.singletonList(object.getObjectId()));
988             }
989             else {
990                 ObjEntity entity = context.getEntityResolver().lookupObjEntity(object);
991                 DataRowUtils.refreshObjectWithSnapshot(entity, object, snapshot, true);
992
993                 if (object.getPersistenceState() == PersistenceState.HOLLOW) {
994                     object.setPersistenceState(PersistenceState.COMMITTED);
995                 }
996             }
997         }
998     }
999
1000    /**
1001     * @since 1.1
1002     */

1003    void processDeletedIDs(Collection JavaDoc deletedIDs) {
1004        if (deletedIDs != null && !deletedIDs.isEmpty()) {
1005            Iterator JavaDoc it = deletedIDs.iterator();
1006            while (it.hasNext()) {
1007                ObjectId oid = (ObjectId) it.next();
1008                DataObject object = getObject(oid);
1009
1010                if (object == null) {
1011                    continue;
1012                }
1013
1014                DataContextDelegate delegate;
1015
1016                // TODO: refactor "switch" to avoid code duplication
1017

1018                switch (object.getPersistenceState()) {
1019                    case PersistenceState.COMMITTED:
1020                    case PersistenceState.HOLLOW:
1021                    case PersistenceState.DELETED:
1022
1023                        // consult delegate
1024
delegate = object.getDataContext().nonNullDelegate();
1025
1026                        if (delegate.shouldProcessDelete(object)) {
1027                            objectMap.remove(oid);
1028                            retainedSnapshotMap.remove(oid);
1029
1030                            // setting DataContext to null will also set
1031
// state to transient
1032
object.setDataContext(null);
1033                            delegate.finishedProcessDelete(object);
1034                        }
1035
1036                        break;
1037
1038                    case PersistenceState.MODIFIED:
1039
1040                        // consult delegate
1041
delegate = object.getDataContext().nonNullDelegate();
1042                        if (delegate.shouldProcessDelete(object)) {
1043                            object.setPersistenceState(PersistenceState.NEW);
1044                            delegate.finishedProcessDelete(object);
1045                        }
1046
1047                        break;
1048                }
1049            }
1050        }
1051    }
1052
1053    /**
1054     * @since 1.1
1055     */

1056    void processInvalidatedIDs(Collection JavaDoc invalidatedIDs) {
1057        if (invalidatedIDs != null && !invalidatedIDs.isEmpty()) {
1058            Iterator JavaDoc it = invalidatedIDs.iterator();
1059            while (it.hasNext()) {
1060                ObjectId oid = (ObjectId) it.next();
1061                DataObject object = getObject(oid);
1062
1063                if (object == null) {
1064                    continue;
1065                }
1066
1067                // TODO: refactor "switch" to avoid code duplication
1068

1069                switch (object.getPersistenceState()) {
1070                    case PersistenceState.COMMITTED:
1071                        object.setPersistenceState(PersistenceState.HOLLOW);
1072                        break;
1073                    case PersistenceState.MODIFIED:
1074                        DataContext context = object.getDataContext();
1075                        DataRow diff = getSnapshot(oid, context);
1076                        // consult delegate if it exists
1077
DataContextDelegate delegate = context.nonNullDelegate();
1078                        if (delegate.shouldMergeChanges(object, diff)) {
1079                            ObjEntity entity = context
1080                                    .getEntityResolver()
1081                                    .lookupObjEntity(object);
1082                            DataRowUtils.forceMergeWithSnapshot(entity, object, diff);
1083                            delegate.finishedMergeChanges(object);
1084                        }
1085                    
1086                    case PersistenceState.HOLLOW:
1087                        // do nothing
1088
break;
1089
1090                    case PersistenceState.DELETED:
1091                        // TODO: Do nothing? Or treat as merged?
1092
break;
1093                }
1094            }
1095        }
1096    }
1097
1098    /**
1099     * @since 1.1
1100     */

1101    void processIndirectlyModifiedIDs(Collection JavaDoc indirectlyModifiedIDs) {
1102        Iterator JavaDoc indirectlyModifiedIt = indirectlyModifiedIDs.iterator();
1103        while (indirectlyModifiedIt.hasNext()) {
1104            ObjectId oid = (ObjectId) indirectlyModifiedIt.next();
1105
1106            DataObject object = getObject(oid);
1107
1108            if (object == null
1109                    || object.getPersistenceState() != PersistenceState.COMMITTED) {
1110                continue;
1111            }
1112
1113            // for now "break" all "independent" object relationships...
1114
// in the future we may want to be more precise and go after modified
1115
// relationships only, or even process updated lists without invalidating...
1116

1117            DataContextDelegate delegate = object.getDataContext().nonNullDelegate();
1118
1119            if (delegate.shouldMergeChanges(object, null)) {
1120                ObjEntity entity = object
1121                        .getDataContext()
1122                        .getEntityResolver()
1123                        .lookupObjEntity(object);
1124                Iterator JavaDoc relationshipIterator = entity.getRelationships().iterator();
1125                while (relationshipIterator.hasNext()) {
1126                    ObjRelationship relationship = (ObjRelationship) relationshipIterator
1127                            .next();
1128
1129                    if (relationship.isSourceIndependentFromTargetChange()) {
1130                        Object JavaDoc fault = relationship.isToMany()
1131                                ? Fault.getToManyFault()
1132                                : Fault.getToOneFault();
1133                        object.writePropertyDirectly(relationship.getName(), fault);
1134                    }
1135                }
1136
1137                delegate.finishedProcessDelete(object);
1138            }
1139        }
1140    }
1141
1142    /**
1143     * @since 1.1
1144     */

1145    void processUpdatedSnapshots(Map JavaDoc diffs) {
1146        if (diffs != null && !diffs.isEmpty()) {
1147            Iterator JavaDoc oids = diffs.entrySet().iterator();
1148
1149            while (oids.hasNext()) {
1150                Map.Entry JavaDoc entry = (Map.Entry JavaDoc) oids.next();
1151
1152                ObjectId oid = (ObjectId) entry.getKey();
1153                DataObject object = getObject(oid);
1154
1155                // no object, or HOLLOW object require no processing
1156
if (object == null
1157                        || object.getPersistenceState() == PersistenceState.HOLLOW) {
1158                    continue;
1159                }
1160
1161                DataRow diff = (DataRow) entry.getValue();
1162
1163                // perform same steps as resolveHollow()
1164
if (object.getPersistenceState() == PersistenceState.COMMITTED) {
1165                    // consult delegate if it exists
1166
DataContextDelegate delegate = object
1167                            .getDataContext()
1168                            .nonNullDelegate();
1169                    if (delegate.shouldMergeChanges(object, diff)) {
1170                        ObjEntity entity = object.getDataContext().getEntityResolver().lookupObjEntity(object);
1171                        DataRow snapshot = getSnapshot(object.getObjectId(), object.getDataContext());
1172                        DataRowUtils.refreshObjectWithSnapshot(entity, object, snapshot, true);
1173                        delegate.finishedMergeChanges(object);
1174                    }
1175                    continue;
1176                }
1177
1178                // merge modified and deleted
1179
if (object.getPersistenceState() == PersistenceState.DELETED
1180                        || object.getPersistenceState() == PersistenceState.MODIFIED) {
1181
1182                    // consult delegate if it exists
1183
DataContextDelegate delegate = object
1184                            .getDataContext()
1185                            .nonNullDelegate();
1186                    if (delegate.shouldMergeChanges(object, diff)) {
1187                        ObjEntity entity = object
1188                                .getDataContext()
1189                                .getEntityResolver()
1190                                .lookupObjEntity(object);
1191                        DataRowUtils.forceMergeWithSnapshot(entity, object, diff);
1192                        delegate.finishedMergeChanges(object);
1193                    }
1194                }
1195            }
1196        }
1197    }
1198
1199    /**
1200     * Records the fact that flattened relationship was created.
1201     *
1202     * @since 1.1
1203     */

1204    void flattenedRelationshipSet(
1205            DataObject source,
1206            ObjRelationship relationship,
1207            DataObject destination) {
1208
1209        if (!relationship.isFlattened()) {
1210            return;
1211        }
1212
1213        if (relationship.isReadOnly()) {
1214            throw new CayenneRuntimeException(
1215                    "Cannot set the read-only flattened relationship "
1216                            + relationship.getName());
1217        }
1218
1219        // Register this combination (so we can remove it later if an insert occurs before
1220
// commit)
1221
FlattenedRelationshipUpdate info = new FlattenedRelationshipUpdate(
1222                source,
1223                destination,
1224                relationship);
1225
1226        // If this combination has already been deleted, simply undelete it.
1227
if (!flattenedDeletes.remove(info) && !flattenedInserts.contains(info)) {
1228            flattenedInserts.add(info);
1229        }
1230    }
1231
1232    /**
1233     * Records the fact that flattened relationship was broken down.
1234     *
1235     * @since 1.1
1236     */

1237    void flattenedRelationshipUnset(
1238            DataObject source,
1239            ObjRelationship relationship,
1240            DataObject destination) {
1241
1242        if (!relationship.isFlattened()) {
1243            return;
1244        }
1245
1246        if (relationship.isReadOnly()) {
1247            throw new CayenneRuntimeException(
1248                    "Cannot unset the read-only flattened relationship "
1249                            + relationship.getName());
1250        }
1251
1252        // Register this combination,
1253
// so we can remove it later if an insert occurs before commit
1254
FlattenedRelationshipUpdate info = new FlattenedRelationshipUpdate(
1255                source,
1256                destination,
1257                relationship);
1258
1259        // If this combination has already been inserted, simply "uninsert" it
1260
// also do not delete it twice
1261
if (!flattenedInserts.remove(info) && !flattenedDeletes.contains(info)) {
1262            flattenedDeletes.add(info);
1263        }
1264    }
1265
1266    List JavaDoc getFlattenedInserts() {
1267        return flattenedInserts;
1268    }
1269
1270    List JavaDoc getFlattenedDeletes() {
1271        return flattenedDeletes;
1272    }
1273}
Popular Tags